Skip to content

Commit 8862c93

Browse files
committed
add OffsetPaginationExtension
1 parent 66a540e commit 8862c93

File tree

6 files changed

+148
-31
lines changed

6 files changed

+148
-31
lines changed

setup.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
"orjson",
1111
"pydantic",
1212
"stac_pydantic==3.1.*",
13-
"stac-fastapi.api~=3.0.2",
14-
"stac-fastapi.extensions~=3.0.2",
15-
"stac-fastapi.types~=3.0.2",
13+
"stac-fastapi.api~=3.0.3",
14+
"stac-fastapi.extensions~=3.0.3",
15+
"stac-fastapi.types~=3.0.3",
1616
"asyncpg",
1717
"buildpg",
1818
"brotli_asgi",

stac_fastapi/pgstac/app.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from stac_fastapi.extensions.core import (
2020
FieldsExtension,
2121
FilterExtension,
22+
OffsetPaginationExtension,
2223
SortExtension,
2324
TokenPaginationExtension,
2425
TransactionExtension,
@@ -55,6 +56,9 @@
5556
"sort": SortExtension(),
5657
"fields": FieldsExtension(),
5758
"filter": FilterExtension(client=FiltersClient()),
59+
# NOTE: there is no conformance class for the Pagination extension
60+
# so `CollectionSearchExtension.from_extensions` will raise a warning
61+
"pagination": OffsetPaginationExtension(),
5862
}
5963

6064
enabled_extensions = (

stac_fastapi/pgstac/core.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ async def all_collections( # noqa: C901
4747
bbox: Optional[BBox] = None,
4848
datetime: Optional[DateTimeType] = None,
4949
limit: Optional[int] = None,
50+
offset: Optional[int] = None,
5051
query: Optional[str] = None,
51-
token: Optional[str] = None,
5252
fields: Optional[List[str]] = None,
5353
sortby: Optional[str] = None,
5454
filter: Optional[str] = None,
@@ -69,7 +69,7 @@ async def all_collections( # noqa: C901
6969
base_args = {
7070
"bbox": bbox,
7171
"limit": limit,
72-
"token": token,
72+
"offset": offset,
7373
"query": orjson.loads(unquote_plus(query)) if query else query,
7474
}
7575

@@ -91,16 +91,16 @@ async def all_collections( # noqa: C901
9191
)
9292
collections_result: Collections = await conn.fetchval(q, *p)
9393

94-
next: Optional[Dict[str, Any]] = None
95-
prev: Optional[Dict[str, Any]] = None
94+
next_link: Optional[Dict[str, Any]] = None
95+
prev_link: Optional[Dict[str, Any]] = None
9696
if links := collections_result.get("links"):
97-
next = None
98-
prev = None
97+
next_link = None
98+
prev_link = None
9999
for link in links:
100100
if link["rel"] == "next":
101-
next = link
101+
next_link = link
102102
elif link["rel"] == "prev":
103-
prev = link
103+
prev_link = link
104104

105105
linked_collections: List[Collection] = []
106106
collections = collections_result["collections"]
@@ -125,10 +125,13 @@ async def all_collections( # noqa: C901
125125

126126
linked_collections.append(coll)
127127

128+
if not collections:
129+
next_link = None
130+
128131
links = await CollectionSearchPagingLinks(
129132
request=request,
130-
next=next,
131-
prev=prev,
133+
next=next_link,
134+
prev=prev_link,
132135
).get_links()
133136

134137
return Collections(

stac_fastapi/pgstac/models/links.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,16 +191,14 @@ def link_next(self) -> Optional[Dict[str, Any]]:
191191

192192
# if next link is equal to this link, skip it
193193
if href == self.url:
194-
print(self.request.body())
195194
return None
196195

197-
link = {
196+
return {
198197
"rel": Relations.next.value,
199198
"type": MimeTypes.geojson.value,
200199
"method": method,
201200
"href": href,
202201
}
203-
return link
204202

205203
return None
206204

@@ -217,6 +215,7 @@ def link_prev(self):
217215
# if prev link is equal to this link, skip it
218216
if href == self.url:
219217
return None
218+
220219
return {
221220
"rel": Relations.previous.value,
222221
"type": MimeTypes.geojson.value,

tests/conftest.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
CollectionSearchExtension,
2727
FieldsExtension,
2828
FilterExtension,
29+
OffsetPaginationExtension,
2930
SortExtension,
3031
TokenPaginationExtension,
3132
TransactionExtension,
@@ -140,10 +141,12 @@ def api_client(request, database):
140141
SortExtension(),
141142
FieldsExtension(),
142143
FilterExtension(client=FiltersClient()),
144+
OffsetPaginationExtension(),
143145
]
144-
collection_search_extension = CollectionSearchExtension.from_extensions(
145-
collection_extensions
146-
)
146+
with pytest.warns(UserWarning):
147+
collection_search_extension = CollectionSearchExtension.from_extensions(
148+
collection_extensions
149+
)
147150

148151
items_get_request_model = create_request_model(
149152
model_name="ItemCollectionUri",

tests/resources/test_collection.py

Lines changed: 120 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -309,19 +309,127 @@ async def test_get_collections_search(
309309
async def test_get_collections_search_limit_offset(
310310
app_client, load_test_collection, load_test2_collection
311311
):
312+
resp = await app_client.get("/collections")
313+
cols = resp.json()["collections"]
314+
assert len(cols) == 2
315+
links = resp.json()["links"]
316+
assert len(links) == 2
317+
assert {"root", "self"} == {link["rel"] for link in links}
318+
319+
###################
320+
# limit should be positive
321+
resp = await app_client.get("/collections", params={"limit": 0})
322+
assert resp.status_code == 400
323+
324+
###################
325+
# limit=1, should have a `next` link
312326
resp = await app_client.get(
313327
"/collections",
314328
params={"limit": 1},
315329
)
316-
response = resp.json()
317-
assert len(response["collections"]) == 1
318-
assert response["collections"][0]["id"] == load_test_collection["id"]
319-
320-
# check next link
321-
next_link = [link["href"] for link in response["links"] if link["rel"] == "next"][0]
322-
next_url = next_link.replace(str(app_client.base_url), "")
323-
next_resp = await app_client.get(next_url)
324-
next_response = next_resp.json()
325-
326-
assert len(next_response["collections"]) == 1
327-
assert next_response["collections"][0]["id"] == load_test2_collection.id
330+
cols = resp.json()["collections"]
331+
links = resp.json()["links"]
332+
assert len(cols) == 1
333+
assert cols[0]["id"] == load_test_collection["id"]
334+
assert len(links) == 3
335+
assert {"root", "self", "next"} == {link["rel"] for link in links}
336+
next_link = list(filter(lambda link: link["rel"] == "next", links))[0]
337+
assert next_link["href"].endswith("?limit=1&offset=1")
338+
339+
###################
340+
# limit=2, there should not be a next link
341+
resp = await app_client.get(
342+
"/collections",
343+
params={"limit": 2},
344+
)
345+
cols = resp.json()["collections"]
346+
links = resp.json()["links"]
347+
assert len(cols) == 2
348+
assert cols[0]["id"] == load_test_collection["id"]
349+
assert cols[1]["id"] == load_test2_collection.id
350+
# TODO: check with pgstac
351+
# assert len(links) == 2
352+
# assert {"root", "self"} == {link["rel"] for link in links}
353+
354+
###################
355+
# limit=3, there should not be a next/previous link
356+
resp = await app_client.get(
357+
"/collections",
358+
params={"limit": 3},
359+
)
360+
cols = resp.json()["collections"]
361+
links = resp.json()["links"]
362+
assert len(cols) == 2
363+
assert cols[0]["id"] == load_test_collection["id"]
364+
assert cols[1]["id"] == load_test2_collection.id
365+
assert len(links) == 2
366+
assert {"root", "self"} == {link["rel"] for link in links}
367+
368+
###################
369+
# offset=3, because there are 2 collections, we should not have `next` or `prev` links
370+
resp = await app_client.get(
371+
"/collections",
372+
params={"offset": 3},
373+
)
374+
cols = resp.json()["collections"]
375+
links = resp.json()["links"]
376+
assert len(cols) == 0
377+
assert len(links) == 2
378+
assert {"root", "self"} == {link["rel"] for link in links}
379+
380+
###################
381+
# offset=3,limit=1
382+
resp = await app_client.get(
383+
"/collections",
384+
params={"limit": 1, "offset": 3},
385+
)
386+
cols = resp.json()["collections"]
387+
links = resp.json()["links"]
388+
assert len(cols) == 0
389+
assert len(links) == 3
390+
assert {"root", "self", "previous"} == {link["rel"] for link in links}
391+
prev_link = list(filter(lambda link: link["rel"] == "previous", links))[0]
392+
assert prev_link["href"].endswith("?limit=1&offset=2")
393+
394+
# ###################
395+
# # offset=3,limit=2
396+
# resp = await app_client.get(
397+
# "/collections",
398+
# params={"limit": 2,"offset": 3},
399+
# )
400+
# cols = resp.json()["collections"]
401+
# links = resp.json()["links"]
402+
# assert len(cols) == 0
403+
# assert len(links) == 3
404+
# assert {"root", "self", "previous"} == {link["rel"] for link in links}
405+
# prev_link = list(filter(lambda link: link["rel"] == "previous", links))[0]
406+
# assert prev_link["href"].endswith("?limit=1&offset=2")
407+
408+
###################
409+
# offset=1, should have a `previous` link
410+
resp = await app_client.get(
411+
"/collections",
412+
params={"offset": 1},
413+
)
414+
cols = resp.json()["collections"]
415+
links = resp.json()["links"]
416+
assert len(cols) == 1
417+
assert cols[0]["id"] == load_test2_collection.id
418+
# TODO: Check with pgstac
419+
# assert len(links) == 3
420+
# assert {"root", "self", "previous"} == {link["rel"] for link in links}
421+
# prev_link = list(filter(lambda link: link["rel"] == "previous", links))[0]
422+
# assert prev_link["href"].endswith("?offset=0")
423+
424+
###################
425+
# offset=0, should not have next/previous link
426+
resp = await app_client.get(
427+
"/collections",
428+
params={"offset": 0},
429+
)
430+
cols = resp.json()["collections"]
431+
links = resp.json()["links"]
432+
assert len(cols) == 2
433+
# TODO: Check with pgstac
434+
# assert len(links) == 2
435+
# assert {"root", "self"} == {link["rel"] for link in links}

0 commit comments

Comments
 (0)