Skip to content

Commit 1157af8

Browse files
authored
feat: add filter extension for items_collection (#77)
add the FilterExtension already available for the search endpoint to the items endpoint
1 parent c619501 commit 1157af8

3 files changed

Lines changed: 80 additions & 6 deletions

File tree

stac_fastapi/eodag/app.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
itm_col_extensions_map = {
125125
"token": TokenPaginationExtension(),
126126
"sort": SortExtension(conformance_classes=[SortConformanceClasses.ITEMS]),
127+
"filter": FilterExtension(client=FiltersClient(stac_metadata_model=stac_metadata_model)),
127128
}
128129

129130
all_extensions = {

stac_fastapi/eodag/core.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,8 @@ async def item_collection(
373373
limit: Optional[int] = None,
374374
page: Optional[str] = None,
375375
sortby: Optional[list[str]] = None,
376+
filter_expr: Optional[str] = None,
377+
filter_lang: Optional[str] = "cql2-text",
376378
**kwargs: Any,
377379
) -> ItemCollection:
378380
"""
@@ -386,6 +388,9 @@ async def item_collection(
386388
:param datetime: Date and time range to filter the items.
387389
:param limit: Maximum number of items to return.
388390
:param page: Page token for pagination.
391+
:param sortby: List of fields to sort the results by.
392+
:param filter_expr: CQL filter to apply to the search.
393+
:param filter_lang: Language of the filter (default is "cql2-text").
389394
:param kwargs: Additional arguments.
390395
:returns: An ItemCollection.
391396
:raises NotFoundError: If the collection does not exist.
@@ -405,6 +410,9 @@ async def item_collection(
405410
sortby_converted = get_sortby_to_post(sortby)
406411
base_args["sortby"] = cast(Any, sortby_converted)
407412

413+
if filter_expr:
414+
add_filter_to_args(base_args, filter_lang, filter_expr)
415+
408416
clean = {}
409417
for k, v in base_args.items():
410418
if v is not None and v != []:
@@ -481,12 +489,7 @@ def get_search(
481489
base_args["datetime"] = format_datetime_range(datetime)
482490

483491
if filter_expr:
484-
if filter_lang == "cql2-text":
485-
filter_expr = to_cql2(parse_cql2_text(filter_expr))
486-
filter_lang = "cql2-json"
487-
488-
base_args["filter"] = str2json("filter_expr", filter_expr)
489-
base_args["filter_lang"] = "cql2-json"
492+
add_filter_to_args(base_args, filter_lang, filter_expr)
490493

491494
# Remove None values from dict
492495
clean = {}
@@ -766,3 +769,18 @@ def eodag_search_next_page(dag, eodag_args):
766769
logger.info("StopIteration encountered during next page search.")
767770
search_result = SearchResult([])
768771
return search_result
772+
773+
774+
def add_filter_to_args(base_args: dict[str, Any], filter_lang: Optional[str], filter_expr: Optional[str]):
775+
"""Parse the filter from the query and add to arguments
776+
777+
:param base_args:
778+
:param filter_expr: CQL filter to apply to the search.
779+
:param filter_lang: Language of the filter (default is "cql2-text").
780+
"""
781+
if filter_lang == "cql2-text":
782+
filter_expr = to_cql2(parse_cql2_text(filter_expr))
783+
filter_lang = "cql2-json"
784+
785+
base_args["filter"] = str2json("filter_expr", filter_expr)
786+
base_args["filter_lang"] = "cql2-json"

tests/test_search.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,61 @@ async def test_date_search_from_items(request_valid, defaults, use_dates):
290290
)
291291

292292

293+
async def test_filter_extension_items(request_valid, defaults, mock_search):
294+
"""Search through eodag server /items endpoint using the filter extension should return a valid response"""
295+
296+
# one parameter
297+
expected_kwargs = {"sat:absolute_orbit": 1234}
298+
await request_valid(
299+
f"collections/{defaults.collection}/items?bbox={defaults.bbox_csv}&filter=sat:absolute_orbit=1234",
300+
expected_search_kwargs=dict(
301+
collection=defaults.collection,
302+
token=None,
303+
items_per_page=DEFAULT_ITEMS_PER_PAGE,
304+
geom=defaults.bbox_wkt,
305+
raise_errors=False,
306+
count=False,
307+
validate=True,
308+
**expected_kwargs,
309+
),
310+
)
311+
mock_search.reset_mock()
312+
313+
# two parameters connected with 'and'
314+
expected_kwargs = {"sat:absolute_orbit": 1234, "processing:level": "S2MSIL1C"}
315+
filter_expr = "filter=sat:absolute_orbit=1234 AND processing:level='S2MSIL1C'"
316+
await request_valid(
317+
f"collections/{defaults.collection}/items?bbox={defaults.bbox_csv}&{filter_expr}",
318+
expected_search_kwargs=dict(
319+
collection=defaults.collection,
320+
token=None,
321+
items_per_page=DEFAULT_ITEMS_PER_PAGE,
322+
geom=defaults.bbox_wkt,
323+
raise_errors=False,
324+
count=False,
325+
validate=True,
326+
**expected_kwargs,
327+
),
328+
)
329+
mock_search.reset_mock()
330+
331+
# with IN
332+
expected_kwargs = {"instruments": ["MSI"]}
333+
await request_valid(
334+
f"collections/{defaults.collection}/items?bbox={defaults.bbox_csv}&filter=instruments IN ('MSI')",
335+
expected_search_kwargs=dict(
336+
collection=defaults.collection,
337+
token=None,
338+
items_per_page=DEFAULT_ITEMS_PER_PAGE,
339+
geom=defaults.bbox_wkt,
340+
raise_errors=False,
341+
count=False,
342+
validate=True,
343+
**expected_kwargs,
344+
),
345+
)
346+
347+
293348
@pytest.mark.parametrize(
294349
"sortby,expected_sort_by",
295350
[

0 commit comments

Comments
 (0)