Skip to content

fix collection-search POST model #803

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## [Unreleased]

### Fixed

- Fix collection-search POST request model:
- Fix pydantic model to make sure class variables `_start_date` and `_end_date` not edited (ported from stac-pydantic)
- Fix bbox validation to allow anti-meridian crossing (ported from stac-pydantic)

## [5.0.2] - 2025-01-30

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import attr
from fastapi import Query
from pydantic import BaseModel, Field, field_validator
from pydantic import BaseModel, Field, PrivateAttr, ValidationInfo, field_validator
from stac_pydantic.api.search import SearchDatetime
from stac_pydantic.shared import BBox
from typing_extensions import Annotated
Expand Down Expand Up @@ -64,8 +64,8 @@ class BaseCollectionSearchPostRequest(BaseModel):

# Private properties to store the parsed datetime values.
# Not part of the model schema.
_start_date: Optional[dt] = None
_end_date: Optional[dt] = None
_start_date: Optional[dt] = PrivateAttr(default=None)
_end_date: Optional[dt] = PrivateAttr(default=None)

# Properties to return the private values
@property
Expand Down Expand Up @@ -94,35 +94,33 @@ def validate_bbox(cls, v: BBox) -> BBox:
raise ValueError(
"Maximum elevation must greater than minimum elevation"
)

if xmax < xmin:
raise ValueError(
"Maximum longitude must be greater than minimum longitude"
)
# Validate against WGS84
if xmin < -180 or ymin < -90 or xmax > 180 or ymax > 90:
raise ValueError("Bounding box must be within (-180, -90, 180, 90)")

if ymax < ymin:
raise ValueError(
"Maximum longitude must be greater than minimum longitude"
)

# Validate against WGS84
if xmin < -180 or ymin < -90 or xmax > 180 or ymax > 90:
raise ValueError("Bounding box must be within (-180, -90, 180, 90)")

return v

@field_validator("datetime")
@field_validator("datetime", mode="after")
@classmethod
def validate_datetime(cls, value: str) -> str:
def validate_datetime(
cls, value: Optional[str], info: ValidationInfo
) -> Optional[str]:
"""validate datetime."""
# Split on "/" and replace no value or ".." with None
if value is None:
return value
values = [v if v and v != ".." else None for v in value.split("/")]

# If there are more than 2 dates, it's invalid
if len(values) > 2:
raise ValueError(
"""Invalid datetime range. Too many values.
Must match format: {begin_date}/{end_date}"""
"""Invalid datetime range. Too many values. """
"""Must match format: {begin_date}/{end_date}"""
)

# If there is only one date, duplicate to use for both start and end dates
Expand All @@ -149,8 +147,8 @@ def validate_datetime(cls, value: str) -> str:
)

# Store the parsed dates
cls._start_date = dates[0]
cls._end_date = dates[1]
info.data["_start_date"] = dates[0]
info.data["_end_date"] = dates[1]

# Return the original string value
return value
14 changes: 14 additions & 0 deletions stac_fastapi/extensions/tests/test_collection_search.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
from datetime import datetime, timezone
from urllib.parse import quote_plus

import attr
Expand Down Expand Up @@ -88,6 +89,19 @@ def post_all_collections(
return search_request.model_dump()


def test_datetime_clean():
# ref: https://github.com/stac-utils/stac-pydantic/issues/170
utcnow = datetime.now(timezone.utc)
utcnow_str = utcnow.isoformat()
search = BaseCollectionSearchPostRequest(datetime=utcnow_str)
assert search.start_date == utcnow
assert search.end_date == utcnow

search = BaseCollectionSearchPostRequest()
assert not search.start_date
assert not search.end_date


def test_collection_search_extension_default():
"""Test GET - /collections endpoint with collection-search ext."""
api = StacApi(
Expand Down
Loading