Skip to content

Commit a5354ab

Browse files
committed
pagination, limit fix for catalogs
1 parent 7e72947 commit a5354ab

File tree

2 files changed

+118
-13
lines changed

2 files changed

+118
-13
lines changed

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

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
"""Catalogs extension."""
22

33
from typing import List, Optional, Type
4+
from urllib.parse import urlparse
45

56
import attr
6-
from fastapi import APIRouter, FastAPI, HTTPException, Request
7+
from fastapi import APIRouter, FastAPI, HTTPException, Query, Request
78
from fastapi.responses import JSONResponse
89
from starlette.responses import Response
910

@@ -122,21 +123,37 @@ def register(self, app: FastAPI, settings=None) -> None:
122123

123124
app.include_router(self.router, tags=["Catalogs"])
124125

125-
async def catalogs(self, request: Request) -> Catalog:
126+
async def catalogs(
127+
self,
128+
request: Request,
129+
limit: Optional[int] = Query(
130+
10,
131+
ge=1,
132+
description=(
133+
"The maximum number of catalogs to return (page size). Defaults to 10."
134+
),
135+
),
136+
token: Optional[str] = Query(
137+
None,
138+
description="Pagination token for the next page of results",
139+
),
140+
) -> Catalog:
126141
"""Get root catalog with links to all catalogs.
127142
128143
Args:
129144
request: Request object.
145+
limit: The maximum number of catalogs to return (page size). Defaults to 10.
146+
token: Pagination token for the next page of results.
130147
131148
Returns:
132149
Root catalog containing child links to all catalogs in the database.
133150
"""
134151
base_url = str(request.base_url)
135152

136-
# Get all catalogs from database
153+
# Get all catalogs from database with pagination
137154
catalogs, _, _ = await self.client.database.get_all_catalogs(
138-
token=None,
139-
limit=1000, # Large limit to get all catalogs
155+
token=token,
156+
limit=limit,
140157
request=request,
141158
sort=[{"field": "id", "direction": "asc"}],
142159
)
@@ -258,15 +275,26 @@ async def get_catalog_collections(
258275
if hasattr(catalog, "links") and catalog.links:
259276
for link in catalog.links:
260277
if link.get("rel") in ["child", "item"]:
261-
# Extract collection ID from href
278+
# Extract collection ID from href using proper URL parsing
262279
href = link.get("href", "")
263-
# Look for patterns like /collections/{id} or collections/{id}
264-
if "/collections/" in href:
265-
collection_id = href.split("/collections/")[-1].split("/")[
266-
0
267-
]
268-
if collection_id and collection_id not in collection_ids:
269-
collection_ids.append(collection_id)
280+
if href:
281+
try:
282+
parsed_url = urlparse(href)
283+
path = parsed_url.path
284+
# Look for patterns like /collections/{id} or collections/{id}
285+
if "/collections/" in path:
286+
# Split by /collections/ and take the last segment
287+
path_parts = path.split("/collections/")
288+
if len(path_parts) > 1:
289+
collection_id = path_parts[1].split("/")[0]
290+
if (
291+
collection_id
292+
and collection_id not in collection_ids
293+
):
294+
collection_ids.append(collection_id)
295+
except Exception:
296+
# If URL parsing fails, skip this link
297+
continue
270298

271299
# Fetch the collections
272300
collections = []

stac_fastapi/tests/extensions/test_catalogs.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,3 +301,80 @@ async def test_get_catalog_collection_item_nonexistent_item(
301301
f"/catalogs/{test_catalog['id']}/collections/{ctx.collection['id']}/items/nonexistent-item"
302302
)
303303
assert resp.status_code == 404
304+
305+
306+
@pytest.mark.asyncio
307+
async def test_catalogs_pagination_limit(catalogs_app_client, load_test_data):
308+
"""Test that pagination limit parameter works for catalogs endpoint."""
309+
# Create multiple catalogs
310+
catalog_ids = []
311+
for i in range(5):
312+
test_catalog = load_test_data("test_catalog.json")
313+
test_catalog["id"] = f"test-catalog-{uuid.uuid4()}-{i}"
314+
test_catalog["title"] = f"Test Catalog {i}"
315+
316+
resp = await catalogs_app_client.post("/catalogs", json=test_catalog)
317+
assert resp.status_code == 201
318+
catalog_ids.append(test_catalog["id"])
319+
320+
# Test with limit=2
321+
resp = await catalogs_app_client.get("/catalogs?limit=2")
322+
assert resp.status_code == 200
323+
324+
catalog = resp.json()
325+
child_links = [link for link in catalog["links"] if link["rel"] == "child"]
326+
327+
# Should only return 2 child links
328+
assert len(child_links) == 2
329+
330+
331+
@pytest.mark.asyncio
332+
async def test_catalogs_pagination_default_limit(catalogs_app_client, load_test_data):
333+
"""Test that pagination uses default limit when no limit parameter is provided."""
334+
# Create multiple catalogs
335+
catalog_ids = []
336+
for i in range(15):
337+
test_catalog = load_test_data("test_catalog.json")
338+
test_catalog["id"] = f"test-catalog-{uuid.uuid4()}-{i}"
339+
test_catalog["title"] = f"Test Catalog {i}"
340+
341+
resp = await catalogs_app_client.post("/catalogs", json=test_catalog)
342+
assert resp.status_code == 201
343+
catalog_ids.append(test_catalog["id"])
344+
345+
# Test without limit parameter (should default to 10)
346+
resp = await catalogs_app_client.get("/catalogs")
347+
assert resp.status_code == 200
348+
349+
catalog = resp.json()
350+
child_links = [link for link in catalog["links"] if link["rel"] == "child"]
351+
352+
# Should return default limit of 10 child links
353+
assert len(child_links) == 10
354+
355+
356+
@pytest.mark.asyncio
357+
async def test_catalogs_pagination_limit_validation(catalogs_app_client):
358+
"""Test that pagination limit parameter validation works."""
359+
# Test with limit=0 (should be invalid)
360+
resp = await catalogs_app_client.get("/catalogs?limit=0")
361+
assert resp.status_code == 400 # Validation error returns 400 for Query parameters
362+
363+
364+
@pytest.mark.asyncio
365+
async def test_catalogs_pagination_token_parameter(catalogs_app_client, load_test_data):
366+
"""Test that pagination token parameter is accepted (even if token is invalid)."""
367+
# Create a catalog first
368+
test_catalog = load_test_data("test_catalog.json")
369+
test_catalog["id"] = f"test-catalog-{uuid.uuid4()}"
370+
371+
resp = await catalogs_app_client.post("/catalogs", json=test_catalog)
372+
assert resp.status_code == 201
373+
374+
# Test with token parameter (even if invalid, should be accepted)
375+
resp = await catalogs_app_client.get("/catalogs?token=invalid_token")
376+
assert resp.status_code == 200
377+
378+
catalog = resp.json()
379+
assert catalog["type"] == "Catalog"
380+
assert "links" in catalog

0 commit comments

Comments
 (0)