Skip to content

Commit bc18554

Browse files
committed
return list of catalogs
1 parent c0c4341 commit bc18554

File tree

4 files changed

+81
-83
lines changed

4 files changed

+81
-83
lines changed

stac_fastapi/core/stac_fastapi/core/extensions/catalogs.py

Lines changed: 52 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
import logging
44
from typing import List, Optional, Type
5-
from urllib.parse import urlparse
5+
from urllib.parse import urlencode, urlparse
66

77
import attr
88
from fastapi import APIRouter, FastAPI, HTTPException, Query, Request
99
from fastapi.responses import JSONResponse
1010
from starlette.responses import Response
11+
from typing_extensions import TypedDict
1112

1213
from stac_fastapi.core.models import Catalog
1314
from stac_fastapi.types import stac as stac_types
@@ -17,12 +18,24 @@
1718
logger = logging.getLogger(__name__)
1819

1920

21+
class Catalogs(TypedDict, total=False):
22+
"""Catalogs endpoint response.
23+
24+
Similar to Collections but for catalogs.
25+
"""
26+
27+
catalogs: List[Catalog]
28+
links: List[dict]
29+
numberMatched: Optional[int]
30+
numberReturned: Optional[int]
31+
32+
2033
@attr.s
2134
class CatalogsExtension(ApiExtension):
2235
"""Catalogs Extension.
2336
24-
The Catalogs extension adds a /catalogs endpoint that returns the root catalog
25-
containing child links to all catalogs in the database.
37+
The Catalogs extension adds a /catalogs endpoint that returns a list of all catalogs
38+
in the database, similar to how /collections returns a list of collections.
2639
"""
2740

2841
client: BaseCoreClient = attr.ib(default=None)
@@ -44,10 +57,10 @@ def register(self, app: FastAPI, settings=None) -> None:
4457
path="/catalogs",
4558
endpoint=self.catalogs,
4659
methods=["GET"],
47-
response_model=Catalog,
60+
response_model=Catalogs,
4861
response_class=self.response_class,
49-
summary="Get Root Catalog",
50-
description="Returns the root catalog containing links to all catalogs.",
62+
summary="Get All Catalogs",
63+
description="Returns a list of all catalogs in the database.",
5164
tags=["Catalogs"],
5265
)
5366

@@ -140,73 +153,57 @@ async def catalogs(
140153
None,
141154
description="Pagination token for the next page of results",
142155
),
143-
) -> Catalog:
144-
"""Get root catalog with links to all catalogs.
156+
) -> Catalogs:
157+
"""Get all catalogs with pagination support.
145158
146159
Args:
147160
request: Request object.
148161
limit: The maximum number of catalogs to return (page size). Defaults to 10.
149162
token: Pagination token for the next page of results.
150163
151164
Returns:
152-
Root catalog containing child links to all catalogs in the database.
165+
Catalogs object containing catalogs and pagination links.
153166
"""
154167
base_url = str(request.base_url)
155168

156169
# Get all catalogs from database with pagination
157-
catalogs, _, _ = await self.client.database.get_all_catalogs(
170+
catalogs, next_token, _ = await self.client.database.get_all_catalogs(
158171
token=token,
159172
limit=limit,
160173
request=request,
161174
sort=[{"field": "id", "direction": "asc"}],
162175
)
163176

164-
# Create child links to each catalog
165-
child_links = []
177+
# Convert database catalogs to STAC format
178+
catalog_stac_objects = []
166179
for catalog in catalogs:
167-
catalog_id = catalog.get("id") if isinstance(catalog, dict) else catalog.id
168-
catalog_title = (
169-
catalog.get("title") or catalog_id
170-
if isinstance(catalog, dict)
171-
else catalog.title or catalog.id
172-
)
173-
child_links.append(
174-
{
175-
"rel": "child",
176-
"href": f"{base_url}catalogs/{catalog_id}",
177-
"type": "application/json",
178-
"title": catalog_title,
179-
}
180-
)
181-
182-
# Create root catalog
183-
root_catalog = {
184-
"type": "Catalog",
185-
"stac_version": "1.0.0",
186-
"id": "root",
187-
"title": "Root Catalog",
188-
"description": "Root catalog containing all available catalogs",
189-
"links": [
190-
{
191-
"rel": "self",
192-
"href": f"{base_url}catalogs",
193-
"type": "application/json",
194-
},
195-
{
196-
"rel": "root",
197-
"href": f"{base_url}catalogs",
198-
"type": "application/json",
199-
},
200-
{
201-
"rel": "parent",
202-
"href": base_url.rstrip("/"),
203-
"type": "application/json",
204-
},
205-
]
206-
+ child_links,
207-
}
208-
209-
return Catalog(**root_catalog)
180+
catalog_stac = self.client.catalog_serializer.db_to_stac(catalog, request)
181+
catalog_stac_objects.append(catalog_stac)
182+
183+
# Create pagination links
184+
links = [
185+
{"rel": "root", "type": "application/json", "href": base_url},
186+
{"rel": "parent", "type": "application/json", "href": base_url},
187+
{"rel": "self", "type": "application/json", "href": str(request.url)},
188+
]
189+
190+
# Add next link if there are more pages
191+
if next_token:
192+
query_params = {"limit": limit, "token": next_token}
193+
next_link = {
194+
"rel": "next",
195+
"href": f"{base_url}catalogs?{urlencode(query_params)}",
196+
"type": "application/json",
197+
"title": "Next page of catalogs",
198+
}
199+
links.append(next_link)
200+
201+
# Return Catalogs object with catalogs
202+
return Catalogs(
203+
catalogs=catalog_stac_objects,
204+
links=links,
205+
numberReturned=len(catalog_stac_objects),
206+
)
210207

211208
async def create_catalog(self, catalog: Catalog, request: Request) -> Catalog:
212209
"""Create a new catalog.

stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/database_logic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1776,7 +1776,7 @@ async def get_all_catalogs(
17761776
A tuple of (catalogs, next pagination token if any, optional count).
17771777
"""
17781778
# Define sortable fields for catalogs
1779-
sortable_fields = ["id", "title"]
1779+
sortable_fields = ["id"]
17801780

17811781
# Format the sort parameter
17821782
formatted_sort = []

stac_fastapi/opensearch/stac_fastapi/opensearch/database_logic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1745,7 +1745,7 @@ async def get_all_catalogs(
17451745
A tuple of (catalogs, next pagination token if any, optional count).
17461746
"""
17471747
# Define sortable fields for catalogs
1748-
sortable_fields = ["id", "title"]
1748+
sortable_fields = ["id"]
17491749

17501750
# Format the sort parameter
17511751
formatted_sort = []

stac_fastapi/tests/extensions/test_catalogs.py

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,17 @@
55

66
@pytest.mark.asyncio
77
async def test_get_root_catalog(catalogs_app_client, load_test_data):
8-
"""Test getting the root catalog."""
8+
"""Test getting the catalogs list."""
99
resp = await catalogs_app_client.get("/catalogs")
1010
assert resp.status_code == 200
1111

12-
catalog = resp.json()
13-
assert catalog["type"] == "Catalog"
14-
assert catalog["id"] == "root"
15-
assert catalog["stac_version"] == "1.0.0"
16-
assert "links" in catalog
12+
catalogs_response = resp.json()
13+
assert "catalogs" in catalogs_response
14+
assert "links" in catalogs_response
15+
assert "numberReturned" in catalogs_response
1716

18-
# Check for required links
19-
links = catalog["links"]
17+
# Check for required pagination links
18+
links = catalogs_response["links"]
2019
link_rels = [link["rel"] for link in links]
2120
assert "self" in link_rels
2221
assert "root" in link_rels
@@ -102,7 +101,7 @@ async def test_get_catalog_collections_nonexistent_catalog(catalogs_app_client):
102101

103102
@pytest.mark.asyncio
104103
async def test_root_catalog_with_multiple_catalogs(catalogs_app_client, load_test_data):
105-
"""Test that root catalog includes links to multiple catalogs."""
104+
"""Test that catalogs response includes multiple catalog objects."""
106105
# Create multiple catalogs
107106
catalog_ids = []
108107
for i in range(3):
@@ -114,17 +113,17 @@ async def test_root_catalog_with_multiple_catalogs(catalogs_app_client, load_tes
114113
assert resp.status_code == 201
115114
catalog_ids.append(test_catalog["id"])
116115

117-
# Get root catalog
116+
# Get catalogs list
118117
resp = await catalogs_app_client.get("/catalogs")
119118
assert resp.status_code == 200
120119

121-
catalog = resp.json()
122-
child_links = [link for link in catalog["links"] if link["rel"] == "child"]
120+
catalogs_response = resp.json()
121+
returned_catalogs = catalogs_response["catalogs"]
122+
returned_ids = [catalog["id"] for catalog in returned_catalogs]
123123

124-
# Should have child links for all created catalogs
125-
child_hrefs = [link["href"] for link in child_links]
124+
# Should have all created catalogs in the response
126125
for catalog_id in catalog_ids:
127-
assert any(catalog_id in href for href in child_hrefs)
126+
assert catalog_id in returned_ids
128127

129128

130129
@pytest.mark.asyncio
@@ -321,11 +320,12 @@ async def test_catalogs_pagination_limit(catalogs_app_client, load_test_data):
321320
resp = await catalogs_app_client.get("/catalogs?limit=2")
322321
assert resp.status_code == 200
323322

324-
catalog = resp.json()
325-
child_links = [link for link in catalog["links"] if link["rel"] == "child"]
323+
catalogs_response = resp.json()
324+
returned_catalogs = catalogs_response["catalogs"]
326325

327-
# Should only return 2 child links
328-
assert len(child_links) == 2
326+
# Should only return 2 catalogs
327+
assert len(returned_catalogs) == 2
328+
assert catalogs_response["numberReturned"] == 2
329329

330330

331331
@pytest.mark.asyncio
@@ -346,11 +346,12 @@ async def test_catalogs_pagination_default_limit(catalogs_app_client, load_test_
346346
resp = await catalogs_app_client.get("/catalogs")
347347
assert resp.status_code == 200
348348

349-
catalog = resp.json()
350-
child_links = [link for link in catalog["links"] if link["rel"] == "child"]
349+
catalogs_response = resp.json()
350+
returned_catalogs = catalogs_response["catalogs"]
351351

352-
# Should return default limit of 10 child links
353-
assert len(child_links) == 10
352+
# Should return default limit of 10 catalogs
353+
assert len(returned_catalogs) == 10
354+
assert catalogs_response["numberReturned"] == 10
354355

355356

356357
@pytest.mark.asyncio
@@ -375,6 +376,6 @@ async def test_catalogs_pagination_token_parameter(catalogs_app_client, load_tes
375376
resp = await catalogs_app_client.get("/catalogs?token=invalid_token")
376377
assert resp.status_code == 200
377378

378-
catalog = resp.json()
379-
assert catalog["type"] == "Catalog"
380-
assert "links" in catalog
379+
catalogs_response = resp.json()
380+
assert "catalogs" in catalogs_response
381+
assert "links" in catalogs_response

0 commit comments

Comments
 (0)