Skip to content

Commit d395992

Browse files
authored
fix: remove page parameter leftovers (#78)
includes a small refactor Signed-off-by: Aubin Lambaré <aubin.lambare@cs-soprasteria.com>
1 parent 767e89b commit d395992

6 files changed

Lines changed: 298 additions & 369 deletions

File tree

stac_fastapi/eodag/api.py

Lines changed: 0 additions & 58 deletions
This file was deleted.

stac_fastapi/eodag/core.py

Lines changed: 69 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@
2121

2222
import asyncio
2323
import logging
24+
import re
2425
from typing import TYPE_CHECKING, Any, cast
2526
from urllib.parse import unquote_plus
2627

2728
import attr
2829
import orjson
2930
from fastapi import HTTPException
30-
from fastapi.responses import StreamingResponse
3131
from pydantic import ValidationError
3232
from pydantic_core import InitErrorDetails, PydanticCustomError
3333
from pygeofilter.backends.cql2_json import to_cql2
@@ -52,17 +52,14 @@
5252
from stac_fastapi.eodag.constants import DEFAULT_ITEMS_PER_PAGE
5353
from stac_fastapi.eodag.cql_evaluate import EodagEvaluator
5454
from stac_fastapi.eodag.errors import NoMatchingCollection, ResponseSearchError
55+
from stac_fastapi.eodag.models.item import create_stac_item
5556
from stac_fastapi.eodag.models.links import (
5657
CollectionLinks,
5758
CollectionSearchPagingLinks,
5859
ItemCollectionLinks,
5960
PagingLinks,
6061
)
61-
from stac_fastapi.eodag.models.stac_metadata import (
62-
CommonStacMetadata,
63-
create_stac_item,
64-
get_sortby_to_post,
65-
)
62+
from stac_fastapi.eodag.models.stac_metadata import CommonStacMetadata
6663
from stac_fastapi.eodag.utils import (
6764
check_poly_is_point,
6865
dt_range_to_eodag,
@@ -375,10 +372,11 @@ async def item_collection(
375372
bbox: Optional[list[NumType]] = None,
376373
datetime: Optional[str] = None,
377374
limit: Optional[int] = None,
378-
page: Optional[str] = None,
375+
# extensions
379376
sortby: Optional[list[str]] = None,
380377
filter_expr: Optional[str] = None,
381378
filter_lang: Optional[str] = "cql2-text",
379+
token: Optional[str] = None,
382380
**kwargs: Any,
383381
) -> ItemCollection:
384382
"""
@@ -391,10 +389,10 @@ async def item_collection(
391389
:param bbox: Bounding box to filter the items.
392390
:param datetime: Date and time range to filter the items.
393391
:param limit: Maximum number of items to return.
394-
:param page: Page token for pagination.
395392
:param sortby: List of fields to sort the results by.
396393
:param filter_expr: CQL filter to apply to the search.
397394
:param filter_lang: Language of the filter (default is "cql2-text").
395+
:param token: Page token for pagination.
398396
:param kwargs: Additional arguments.
399397
:returns: An ItemCollection.
400398
:raises NotFoundError: If the collection does not exist.
@@ -407,20 +405,10 @@ async def item_collection(
407405
"bbox": bbox,
408406
"datetime": datetime,
409407
"limit": limit,
410-
"page": page,
408+
"token": token,
411409
}
412410

413-
if sortby:
414-
sortby_converted = get_sortby_to_post(sortby)
415-
base_args["sortby"] = cast(Any, sortby_converted)
416-
417-
if filter_expr:
418-
add_filter_to_args(base_args, filter_lang, filter_expr)
419-
420-
clean = {}
421-
for k, v in base_args.items():
422-
if v is not None and v != []:
423-
clean[k] = v
411+
clean = self._clean_search_args(base_args, sortby=sortby, filter_expr=filter_expr, filter_lang=filter_lang)
424412

425413
search_request = self.post_request_model.model_validate(clean)
426414
item_collection = self._search_base(search_request, request)
@@ -448,12 +436,12 @@ def get_search(
448436
collections: Optional[list[str]] = None,
449437
ids: Optional[list[str]] = None,
450438
bbox: Optional[list[NumType]] = None,
439+
intersects: Optional[str] = None,
451440
datetime: Optional[str] = None,
452441
limit: Optional[int] = None,
442+
# Extensions
453443
query: Optional[str] = None,
454-
page: Optional[str] = None,
455444
sortby: Optional[list[str]] = None,
456-
intersects: Optional[str] = None,
457445
filter_expr: Optional[str] = None,
458446
filter_lang: Optional[str] = "cql2-text",
459447
token: Optional[str] = None,
@@ -466,14 +454,14 @@ def get_search(
466454
:param collections: List of collection IDs to include in the search.
467455
:param ids: List of item IDs to include in the search.
468456
:param bbox: Bounding box to filter the search.
457+
:param intersects: GeoJSON geometry to filter the search.
469458
:param datetime: Date and time range to filter the search.
470459
:param limit: Maximum number of items to return.
471460
:param query: Query string to filter the search.
472-
:param page: Page token for pagination.
473461
:param sortby: List of fields to sort the results by.
474-
:param intersects: GeoJSON geometry to filter the search.
475462
:param filter_expr: CQL filter to apply to the search.
476-
:param filter_lang: Language of the filter (default is "cql2-text").
463+
:param filter_lang: Language of the filter.
464+
:param token: Page token for pagination.
477465
:param kwargs: Additional arguments.
478466
:returns: Found items.
479467
:raises HTTPException: If the provided parameters are invalid.
@@ -483,23 +471,18 @@ def get_search(
483471
"ids": ids,
484472
"bbox": bbox,
485473
"limit": limit,
486-
"query": orjson.loads(unquote_plus(query)) if query else query,
487474
"token": token,
488-
"sortby": get_sortby_to_post(sortby),
489-
"intersects": orjson.loads(unquote_plus(intersects)) if intersects else intersects,
490475
}
491476

492-
if datetime:
493-
base_args["datetime"] = format_datetime_range(datetime)
494-
495-
if filter_expr:
496-
add_filter_to_args(base_args, filter_lang, filter_expr)
497-
498-
# Remove None values from dict
499-
clean = {}
500-
for k, v in base_args.items():
501-
if v is not None and v != []:
502-
clean[k] = v
477+
clean = self._clean_search_args(
478+
base_args,
479+
intersects=intersects,
480+
datetime=datetime,
481+
sortby=sortby,
482+
query=query,
483+
filter_expr=filter_expr,
484+
filter_lang=filter_lang,
485+
)
503486

504487
try:
505488
search_request = self.post_request_model(**clean)
@@ -529,40 +512,55 @@ async def get_item(self, item_id: str, collection_id: str, request: Request, **k
529512

530513
return Item(**item_collection["features"][0])
531514

532-
async def download_item(self, item_id: str, collection_id: str, request: Request, **kwargs) -> StreamingResponse:
533-
"""
534-
Download item by ID.
515+
def _clean_search_args(
516+
self,
517+
base_args: dict[str, Any],
518+
intersects: Optional[str] = None,
519+
datetime: Optional[str] = None,
520+
sortby: Optional[list[str]] = None,
521+
query: Optional[str] = None,
522+
filter_expr: Optional[str] = None,
523+
filter_lang: Optional[str] = None,
524+
**kwargs: Any,
525+
) -> dict[str, Any]:
526+
"""Clean up search arguments to match format expected by pgstac"""
527+
if filter_expr:
528+
if filter_lang == "cql2-text":
529+
filter_expr = to_cql2(parse_cql2_text(filter_expr))
530+
filter_lang = "cql2-json"
535531

536-
:param item_id: ID of the item.
537-
:param collection_id: ID of the collection.
538-
:param request: The request object.
539-
:param kwargs: Additional arguments.
540-
:returns: Streaming response for the item download.
541-
"""
542-
product: EOProduct
543-
product, _ = request.app.state.dag.search({"collection": collection_id, "id": item_id})[0]
544-
545-
# when could this really happen ?
546-
if not product.downloader:
547-
download_plugin = request.app.state.dag._plugins_manager.get_download_plugin(product)
548-
auth_plugin = request.app.state.dag._plugins_manager.get_auth_plugin(download_plugin.provider)
549-
product.register_downloader(download_plugin, auth_plugin)
550-
551-
# required for auth. Can be removed when EODAG implements the auth interface
552-
auth = (
553-
product.downloader_auth.authenticate() if product.downloader_auth is not None else product.downloader_auth
554-
)
532+
base_args["filter"] = str2json("filter_expr", filter_expr)
533+
base_args["filter_lang"] = "cql2-json"
555534

556-
if product.downloader is None:
557-
raise HTTPException(status_code=500, detail="No downloader found for this product")
558-
# can we make something more clean here ?
559-
download_stream_dict = product.downloader._stream_download_dict(product, auth=auth)
535+
if datetime:
536+
base_args["datetime"] = format_datetime_range(datetime)
560537

561-
return StreamingResponse(
562-
content=download_stream_dict.content,
563-
headers=download_stream_dict.headers,
564-
media_type=download_stream_dict.media_type,
565-
)
538+
if query:
539+
base_args["query"] = orjson.loads(unquote_plus(query))
540+
541+
if intersects:
542+
base_args["intersects"] = orjson.loads(unquote_plus(intersects))
543+
544+
if sortby:
545+
sort_param = []
546+
for sort in sortby:
547+
sortparts = re.match(r"^([+-]?)(.*)$", sort)
548+
if sortparts:
549+
sort_param.append(
550+
{
551+
"field": sortparts.group(2).strip(),
552+
"direction": "desc" if sortparts.group(1) == "-" else "asc",
553+
}
554+
)
555+
base_args["sortby"] = sort_param
556+
557+
# Remove None values from dict
558+
clean = {}
559+
for k, v in base_args.items():
560+
if v is not None and v != []:
561+
clean[k] = v
562+
563+
return clean
566564

567565

568566
def prepare_search_base_args(search_request: BaseSearchPostRequest, model: type[CommonStacMetadata]) -> dict[str, Any]:
@@ -753,9 +751,7 @@ def eodag_search_next_page(dag, eodag_args):
753751
next_page_token = eodag_args.pop("token", None)
754752
provider = eodag_args.get("provider")
755753
if not next_page_token or not provider:
756-
raise HTTPException(
757-
status_code=500, detail="Missing required token and federation backend for next page search."
758-
)
754+
raise ValueError("Missing required token and federation backend for next page search.")
759755
search_plugin = next(dag._plugins_manager.get_search_plugins(provider=provider))
760756
next_page_token_key = getattr(search_plugin.config, "pagination", {}).get("next_page_token_key", "page")
761757
eodag_args.pop("count", None)
@@ -773,18 +769,3 @@ def eodag_search_next_page(dag, eodag_args):
773769
logger.info("StopIteration encountered during next page search.")
774770
search_result = SearchResult([])
775771
return search_result
776-
777-
778-
def add_filter_to_args(base_args: dict[str, Any], filter_lang: Optional[str], filter_expr: Optional[str]):
779-
"""Parse the filter from the query and add to arguments
780-
781-
:param base_args:
782-
:param filter_expr: CQL filter to apply to the search.
783-
:param filter_lang: Language of the filter (default is "cql2-text").
784-
"""
785-
if filter_lang == "cql2-text":
786-
filter_expr = to_cql2(parse_cql2_text(filter_expr))
787-
filter_lang = "cql2-json"
788-
789-
base_args["filter"] = str2json("filter_expr", filter_expr)
790-
base_args["filter_lang"] = "cql2-json"

stac_fastapi/eodag/extensions/collection_order.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,8 @@
3838

3939
from stac_fastapi.eodag.config import get_settings
4040
from stac_fastapi.eodag.errors import ResponseSearchError
41-
from stac_fastapi.eodag.models.stac_metadata import (
42-
CommonStacMetadata,
43-
create_stac_item,
44-
)
41+
from stac_fastapi.eodag.models.item import create_stac_item
42+
from stac_fastapi.eodag.models.stac_metadata import CommonStacMetadata
4543

4644
logger = logging.getLogger(__name__)
4745

0 commit comments

Comments
 (0)