Skip to content

Commit adbcd32

Browse files
alambareanesson-cs
authored andcommitted
feat(collection-search): enable filter, sortby, and advanced freetext
1 parent a60a643 commit adbcd32

4 files changed

Lines changed: 37 additions & 31 deletions

File tree

pyproject.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ dependencies = [
1515
"orjson",
1616
"pydantic",
1717
"pydantic_core",
18+
"cql2",
1819
"pygeofilter",
1920
"stac-fastapi.api >= 4.0",
2021
"stac-fastapi.extensions",
@@ -65,7 +66,13 @@ explicit_package_bases = true
6566
exclude = ["tests", ".venv"]
6667

6768
[[tool.mypy.overrides]]
68-
module = ["geojson", "pygeofilter", "pygeofilter.*", "stac_fastapi", "stac_fastapi.*"]
69+
module = [
70+
"geojson",
71+
"pygeofilter",
72+
"pygeofilter.*",
73+
"stac_fastapi",
74+
"stac_fastapi.*",
75+
]
6976
ignore_missing_imports = true
7077

7178
[tool.pytest.ini_options]

stac_fastapi/eodag/app.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
)
3939
from stac_fastapi.extensions.core import (
4040
CollectionSearchExtension,
41+
CollectionSearchFilterExtension,
4142
FilterExtension,
4243
FreeTextExtension,
4344
QueryExtension,
@@ -114,9 +115,12 @@
114115

115116
# collection_search extensions
116117
cs_extensions_map = {
117-
"offset-pagination": OffsetPaginationExtension(),
118-
"collection-search": CollectionSearchExtension(),
119-
"free-text": FreeTextExtension(conformance_classes=[FreeTextConformanceClasses.COLLECTIONS]),
118+
"sort": SortExtension(conformance_classes=[SortConformanceClasses.COLLECTIONS]),
119+
"filter": CollectionSearchFilterExtension(client=FiltersClient()),
120+
"free_text": FreeTextExtension(
121+
conformance_classes=[FreeTextConformanceClasses.COLLECTIONS],
122+
),
123+
"pagination": OffsetPaginationExtension(),
120124
}
121125

122126
# item_collection extensions
@@ -127,11 +131,14 @@
127131
"filter": FilterExtension(client=FiltersClient(stac_metadata_model=stac_metadata_model)),
128132
}
129133

134+
collection_search_extension = CollectionSearchExtension.from_extensions(cs_extensions_map.values())
135+
130136
all_extensions = {
131137
**search_extensions_map,
132138
**cs_extensions_map,
133139
**itm_col_extensions_map,
134140
**{
141+
"collection-search": collection_search_extension,
135142
"data-download": DataDownload(),
136143
"collection-order": CollectionOrderExtension(
137144
client=BaseCollectionOrderClient(stac_metadata_model=stac_metadata_model)
@@ -193,13 +200,6 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
193200
search_post_model = create_post_request_model(search_extensions)
194201
search_get_model = create_get_request_model(search_extensions)
195202

196-
collections_model = create_request_model(
197-
"CollectionsRequest",
198-
base_model=EmptyRequest,
199-
extensions=get_enabled_extensions(cs_extensions_map),
200-
request_type="GET",
201-
)
202-
203203
item_collection_model = create_request_model(
204204
"ItemsRequest",
205205
base_model=ItemCollectionUri,
@@ -220,7 +220,7 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
220220
response_class=ORJSONResponse,
221221
search_get_request_model=search_get_model,
222222
search_post_request_model=search_post_model,
223-
collections_get_request_model=collections_model,
223+
collections_get_request_model=collection_search_extension.GET,
224224
items_get_request_model=item_collection_model,
225225
middlewares=[
226226
Middleware(BrotliMiddleware),

stac_fastapi/eodag/core.py

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,12 @@
2626
from urllib.parse import unquote_plus
2727

2828
import attr
29+
import cql2
2930
import orjson
31+
import pygeofilter
3032
from fastapi import HTTPException
3133
from pydantic import ValidationError
3234
from pydantic_core import InitErrorDetails, PydanticCustomError
33-
from pygeofilter.backends.cql2_json import to_cql2
34-
from pygeofilter.parsers.cql2_json import parse as parse_json
35-
from pygeofilter.parsers.cql2_text import parse as parse_cql2_text
3635
from stac_fastapi.api.models import create_post_request_model
3736
from stac_fastapi.types.errors import NotFoundError
3837
from stac_fastapi.types.requests import get_base_url
@@ -202,7 +201,8 @@ async def all_collections(
202201
q: Optional[list[str]] = None,
203202
sortby: Optional[list[str]] = None,
204203
filter_expr: Optional[str] = None,
205-
filter_lang: Optional[str] = "cql2-text",
204+
filter_lang: Optional[str] = None,
205+
**kwargs: Any,
206206
) -> Collections:
207207
"""
208208
Get all collections from EODAG.
@@ -229,10 +229,11 @@ async def all_collections(
229229
cql2_json = None
230230
if filter_expr:
231231
if filter_lang == "cql2-text":
232-
filter_expr = to_cql2(parse_cql2_text(filter_expr))
233-
filter_lang = "cql2-json"
234-
235-
cql2_json = str2json("filter_expr", filter_expr)
232+
cql2_json = cql2.parse_text(filter_expr).to_json()
233+
elif filter_lang == "cql2-json":
234+
cql2_json = str2json("filter_expr", filter_expr)
235+
else:
236+
raise HTTPException(status_code=400, detail=f"Unsupported filter_lang {filter_lang}")
236237

237238
collections = cast(
238239
CollectionsList,
@@ -241,10 +242,10 @@ async def all_collections(
241242
geometry=bbox,
242243
datetime=datetime,
243244
limit=limit,
244-
q=q,
245+
q=" ".join(q) if q else None,
245246
cql2_json=cql2_json,
246247
sortby=sortby,
247-
)
248+
),
248249
)
249250

250251
number_matched = cast(int, collections.number_matched)
@@ -475,7 +476,7 @@ def _clean_search_args(
475476
"""Clean up search arguments to match format expected by pgstac"""
476477
if filter_expr:
477478
if filter_lang == "cql2-text":
478-
filter_expr = to_cql2(parse_cql2_text(filter_expr))
479+
filter_expr = cql2.parse_text(filter_expr).to_json()
479480
filter_lang = "cql2-json"
480481

481482
base_args["filter"] = str2json("filter_expr", filter_expr)
@@ -495,12 +496,10 @@ def _clean_search_args(
495496
for sort in sortby:
496497
sortparts = re.match(r"^([+-]?)(.*)$", sort)
497498
if sortparts:
498-
sort_param.append(
499-
{
500-
"field": sortparts.group(2).strip(),
501-
"direction": "desc" if sortparts.group(1) == "-" else "asc",
502-
}
503-
)
499+
sort_param.append({
500+
"field": sortparts.group(2).strip(),
501+
"direction": "desc" if sortparts.group(1) == "-" else "asc",
502+
})
504503
base_args["sortby"] = sort_param
505504

506505
# Remove None values from dict
@@ -645,7 +644,7 @@ def add_error(error_message: str) -> None:
645644

646645
errors: list[InitErrorDetails] = []
647646
try:
648-
parsing_result = EodagEvaluator().evaluate(parse_json(filter_)) # type: ignore
647+
parsing_result = EodagEvaluator().evaluate(pygeofilter.parse(filter_)) # type: ignore
649648
except (ValueError, NotImplementedError) as e:
650649
add_error(str(e))
651650
raise ValidationError.from_exception_data(title="stac-fastapi-eodag", line_errors=errors) from e

stac_fastapi/eodag/dag.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def fetch_external_stac_collections(
5151
ext_stac_collections: dict[str, dict[str, Any]] = {}
5252

5353
for collection in collections:
54-
file_path = getattr(collection, "eodag_stac_collection", None)
54+
file_path = getattr(collection, "stacCollection", None)
5555
if not file_path:
5656
continue
5757
logger.info(f"Fetching external STAC collection for {collection.id}")

0 commit comments

Comments
 (0)