Skip to content

Commit 1f36485

Browse files
Enable search on /items and add queryables links (#89)
* Enable search on /items and add queryables links * add tests and remove unused * fix * more tests and update changelog --------- Co-authored-by: vincentsarago <[email protected]>
1 parent 7bfb9ce commit 1f36485

File tree

6 files changed

+71
-9
lines changed

6 files changed

+71
-9
lines changed

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## [Unreleased]
44

5+
- Enable filter extension for `GET /items` requests and add `Queryables` links in `/collections` and `/collections/{collection_id}` responses ([#89](https://github.com/stac-utils/stac-fastapi-pgstac/pull/89))
6+
57
## [3.0.0a4] - 2024-07-10
68

79
- Update stac-fastapi libraries to `~=3.0.0b2`

stac_fastapi/pgstac/core.py

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,18 @@ async def all_collections(self, request: Request, **kwargs) -> Collections:
5757
collection_id=coll["id"], request=request
5858
).get_links(extra_links=coll.get("links"))
5959

60+
if self.extension_is_enabled("FilterExtension"):
61+
coll["links"].append(
62+
{
63+
"rel": Relations.queryables.value,
64+
"type": MimeTypes.jsonschema.value,
65+
"title": "Queryables",
66+
"href": urljoin(
67+
base_url, f"collections/{coll['id']}/queryables"
68+
),
69+
}
70+
)
71+
6072
linked_collections.append(coll)
6173

6274
links = [
@@ -109,6 +121,17 @@ async def get_collection(
109121
collection_id=collection_id, request=request
110122
).get_links(extra_links=collection.get("links"))
111123

124+
if self.extension_is_enabled("FilterExtension"):
125+
base_url = get_base_url(request)
126+
collection["links"].append(
127+
{
128+
"rel": Relations.queryables.value,
129+
"type": MimeTypes.jsonschema.value,
130+
"title": "Queryables",
131+
"href": urljoin(base_url, f"collections/{collection_id}/queryables"),
132+
}
133+
)
134+
112135
return Collection(**collection)
113136

114137
async def _get_base_item(
@@ -285,6 +308,14 @@ async def item_collection(
285308
"token": token,
286309
}
287310

311+
if self.extension_is_enabled("FilterExtension"):
312+
filter_lang = kwargs.get("filter_lang", None)
313+
filter = kwargs.get("filter", None)
314+
if filter is not None and filter_lang == "cql2-text":
315+
ast = parse_cql2_text(filter.strip())
316+
base_args["filter"] = orjson.loads(to_cql2(ast))
317+
base_args["filter-lang"] = "cql2-json"
318+
288319
clean = {}
289320
for k, v in base_args.items():
290321
if v is not None and v != []:
@@ -377,14 +408,6 @@ async def get_search( # noqa: C901
377408
Returns:
378409
ItemCollection containing items which match the search criteria.
379410
"""
380-
query_params = str(request.query_params)
381-
382-
# Kludgy fix because using factory does not allow alias for filter-lang
383-
if filter_lang is None:
384-
match = re.search(r"filter-lang=([a-z0-9-]+)", query_params, re.IGNORECASE)
385-
if match:
386-
filter_lang = match.group(1)
387-
388411
# Parse request parameters
389412
base_args = {
390413
"collections": collections,

tests/api/test_api.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ async def test_get_search_content_type(app_client):
7373
assert resp.headers["content-type"] == "application/geo+json"
7474

7575

76+
async def test_landing_links(app_client):
77+
"""test landing page links."""
78+
landing = await app_client.get("/")
79+
assert landing.status_code == 200, landing.text
80+
assert "Queryables" in [link.get("title") for link in landing.json()["links"]]
81+
82+
7683
async def test_get_queryables_content_type(app_client, load_test_collection):
7784
resp = await app_client.get("queryables")
7885
assert resp.headers["content-type"] == "application/schema+json"
@@ -743,6 +750,9 @@ async def test_no_extension(
743750
async with AsyncClient(transport=ASGITransport(app=app)) as client:
744751
landing = await client.get("http://test/")
745752
assert landing.status_code == 200, landing.text
753+
assert "Queryables" not in [
754+
link.get("title") for link in landing.json()["links"]
755+
]
746756

747757
collection = await client.get("http://test/collections/test-collection")
748758
assert collection.status_code == 200, collection.text

tests/conftest.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,10 @@ def api_client(request, database):
137137
items_get_request_model = create_request_model(
138138
model_name="ItemCollectionUri",
139139
base_model=ItemCollectionUri,
140-
mixins=[TokenPaginationExtension().GET],
140+
mixins=[
141+
TokenPaginationExtension().GET,
142+
FilterExtension(client=FiltersClient()).GET,
143+
],
141144
request_type="GET",
142145
)
143146
search_get_request_model = create_get_request_model(extensions)

tests/resources/test_collection.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,3 +260,19 @@ async def test_get_collections_forwarded_header(app_client, load_test_collection
260260
)
261261
for link in resp.json()["links"]:
262262
assert link["href"].startswith("https://test:1234/")
263+
264+
265+
@pytest.mark.asyncio
266+
async def test_get_collections_queryables_links(app_client, load_test_collection):
267+
resp = await app_client.get(
268+
"/collections",
269+
)
270+
assert "Queryables" in [
271+
link.get("title") for link in resp.json()["collections"][0]["links"]
272+
]
273+
274+
collection_id = resp.json()["collections"][0]["id"]
275+
resp = await app_client.get(
276+
f"/collections/{collection_id}",
277+
)
278+
assert "Queryables" in [link.get("title") for link in resp.json()["links"]]

tests/resources/test_item.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,6 +1471,14 @@ async def test_get_filter_cql2text(app_client, load_test_data, load_test_collect
14711471
resp_json = resp.json()
14721472
assert len(resp.json()["features"]) == 0
14731473

1474+
filter = f"proj:epsg={epsg}"
1475+
params = {"filter": filter, "filter-lang": "cql2-text"}
1476+
resp = await app_client.get(
1477+
f"/collections/{test_item['collection']}/items", params=params
1478+
)
1479+
resp_json = resp.json()
1480+
assert len(resp.json()["features"]) == 1
1481+
14741482

14751483
async def test_item_merge_raster_bands(
14761484
app_client, load_test2_item, load_test2_collection

0 commit comments

Comments
 (0)