Skip to content

Commit

Permalink
keep datetime as string type in GET Request models
Browse files Browse the repository at this point in the history
  • Loading branch information
vincentsarago committed Jan 14, 2025
1 parent 6d65525 commit 682d429
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 48 deletions.
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

## Changed

* use `string` type instead of python `datetime.datetime` for datetime parameter in `BaseSearchGetRequest`, `ItemCollectionUri` and `BaseCollectionSearchGetRequest` GET models

## [3.0.5] - 2025-01-10

### Removed
Expand Down
41 changes: 36 additions & 5 deletions stac_fastapi/api/stac_fastapi/api/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Api request/response models."""

from datetime import datetime as dt
from typing import List, Optional, Type, Union

import attr
Expand All @@ -9,14 +10,13 @@
from typing_extensions import Annotated

from stac_fastapi.types.extension import ApiExtension
from stac_fastapi.types.rfc3339 import DateTimeType
from stac_fastapi.types.rfc3339 import str_to_interval
from stac_fastapi.types.search import (
APIRequest,
BaseSearchGetRequest,
BaseSearchPostRequest,
Limit,
_bbox_converter,
_datetime_converter,
)

try:
Expand Down Expand Up @@ -121,9 +121,40 @@ class ItemCollectionUri(APIRequest):
),
] = attr.ib(default=10)
bbox: Optional[BBox] = attr.ib(default=None, converter=_bbox_converter)
datetime: Optional[DateTimeType] = attr.ib(
default=None, converter=_datetime_converter
)
datetime: Annotated[
Optional[str],
Query(
description="""Only return items that have a temporal property that intersects this value.\n
Either a date-time or an interval, open or closed. Date and time expressions adhere to RFC 3339. Open intervals are expressed using double-dots.""", # noqa: E501
openapi_examples={
"datetime": {"value": "2018-02-12T23:20:50Z"},
"closed-interval": {"value": "2018-02-12T00:00:00Z/2018-03-18T12:31:12Z"},
"open-interval-from": {"value": "2018-02-12T00:00:00Z/.."},
"open-interval-to": {"value": "../2018-03-18T12:31:12Z"},
},
),
] = attr.ib(default=None)

@datetime.validator
def validate_datetime(self, attribute, value):
"""Validate Datetime."""
_ = str_to_interval(value)

@property
def start_date(self) -> Optional[dt]:
"""Start Date."""
if self.datetime is None:
return self.datetime
interval = str_to_interval(self.datetime)
return interval if isinstance(interval, dt) else interval[0]

@property
def end_date(self) -> Optional[dt]:
"""End Date."""
if self.datetime is None:
return self.datetime
interval = str_to_interval(self.datetime)
return interval[1] if isinstance(interval, tuple) else None


class GeoJSONResponse(JSONResponse):
Expand Down
33 changes: 26 additions & 7 deletions stac_fastapi/api/tests/test_app.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from datetime import datetime
from typing import List, Optional, Union

import attr
Expand Down Expand Up @@ -141,7 +140,7 @@ def get_search(
ids: Optional[List[str]] = None,
bbox: Optional[List[NumType]] = None,
intersects: Optional[str] = None,
datetime: Optional[Union[str, datetime]] = None,
datetime: Optional[str] = None,
limit: Optional[int] = 10,
filter: Optional[str] = None,
filter_crs: Optional[str] = None,
Expand Down Expand Up @@ -221,7 +220,7 @@ def get_search(
ids: Optional[List[str]] = None,
bbox: Optional[List[NumType]] = None,
intersects: Optional[str] = None,
datetime: Optional[Union[str, datetime]] = None,
datetime: Optional[str] = None,
limit: Optional[int] = 10,
**kwargs,
) -> stac.ItemCollection:
Expand All @@ -247,7 +246,7 @@ def item_collection(
self,
collection_id: str,
bbox: Optional[List[Union[float, int]]] = None,
datetime: Optional[Union[str, datetime]] = None,
datetime: Optional[str] = None,
limit: int = 10,
token: str = None,
**kwargs,
Expand Down Expand Up @@ -392,6 +391,7 @@ def test_client_datetime_input_params():

class FakeClient(BaseCoreClient):
def post_search(self, search_request: BaseSearchPostRequest, **kwargs):
assert isinstance(search_request.datetime, str)
return search_request.datetime

def get_search(
Expand All @@ -400,10 +400,11 @@ def get_search(
ids: Optional[List[str]] = None,
bbox: Optional[List[NumType]] = None,
intersects: Optional[str] = None,
datetime: Optional[Union[str, datetime]] = None,
datetime: Optional[str] = None,
limit: Optional[int] = 10,
**kwargs,
):
assert isinstance(datetime, str)
return datetime

def get_item(self, item_id: str, collection_id: str, **kwargs) -> stac.Item:
Expand All @@ -419,7 +420,7 @@ def item_collection(
self,
collection_id: str,
bbox: Optional[List[Union[float, int]]] = None,
datetime: Optional[Union[str, datetime]] = None,
datetime: Optional[str] = None,
limit: int = 10,
token: str = None,
**kwargs,
Expand All @@ -439,16 +440,34 @@ def item_collection(
"datetime": "2020-01-01T00:00:00.00001Z",
},
)
get_search_zero = client.get(
"/search",
params={
"collections": ["test"],
"datetime": "2020-01-01T00:00:00.0000Z",
},
)
post_search = client.post(
"/search",
json={
"collections": ["test"],
"datetime": "2020-01-01T00:00:00.00001Z",
},
)
post_search_zero = client.post(
"/search",
json={
"collections": ["test"],
"datetime": "2020-01-01T00:00:00.0000Z",
},
)

assert get_search.status_code == 200, get_search.text
assert get_search.json() == "2020-01-01T00:00:00.000010+00:00"
assert get_search.json() == "2020-01-01T00:00:00.00001Z"
assert get_search_zero.status_code == 200, get_search_zero.text
assert get_search_zero.json() == "2020-01-01T00:00:00.0000Z"

assert post_search.status_code == 200, post_search.text
assert post_search.json() == "2020-01-01T00:00:00.00001Z"
assert post_search_zero.status_code == 200, post_search_zero.text
assert post_search_zero.json() == "2020-01-01T00:00:00.0000Z"
3 changes: 2 additions & 1 deletion stac_fastapi/api/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ def test_create_get_request_model():

assert model.collections == ["test1", "test2"]
assert model.filter_crs == "epsg:4326"
d = model.datetime
d = model.start_date
assert d.microsecond == 10
assert not model.end_date

with pytest.raises(HTTPException):
request_model(datetime="yo")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@
from stac_pydantic.shared import BBox
from typing_extensions import Annotated

from stac_fastapi.types.rfc3339 import DateTimeType
from stac_fastapi.types.rfc3339 import str_to_interval
from stac_fastapi.types.search import (
APIRequest,
Limit,
_bbox_converter,
_datetime_converter,
)


Expand All @@ -24,22 +23,69 @@ class BaseCollectionSearchGetRequest(APIRequest):
"""Basics additional Collection-Search parameters for the GET request."""

bbox: Optional[BBox] = attr.ib(default=None, converter=_bbox_converter)
datetime: Optional[DateTimeType] = attr.ib(
default=None, converter=_datetime_converter
)
datetime: Annotated[
Optional[str],
Query(
description="""Only return items that have a temporal property that intersects this value.\n
Either a date-time or an interval, open or closed. Date and time expressions adhere to RFC 3339. Open intervals are expressed using double-dots.""", # noqa: E501
openapi_examples={
"datetime": {"value": "2018-02-12T23:20:50Z"},
"closed-interval": {"value": "2018-02-12T00:00:00Z/2018-03-18T12:31:12Z"},
"open-interval-from": {"value": "2018-02-12T00:00:00Z/.."},
"open-interval-to": {"value": "../2018-03-18T12:31:12Z"},
},
),
] = attr.ib(default=None)
limit: Annotated[
Optional[Limit],
Query(
description="Limits the number of results that are included in each page of the response." # noqa: E501
),
] = attr.ib(default=10)

@datetime.validator
def validate_datetime(self, attribute, value):
"""Validate Datetime."""
_ = str_to_interval(value)

@property
def start_date(self) -> Optional[dt]:
"""Start Date."""
if self.datetime is None:
return self.datetime
interval = str_to_interval(self.datetime)
return interval if isinstance(interval, dt) else interval[0]

@property
def end_date(self) -> Optional[dt]:
"""End Date."""
if self.datetime is None:
return self.datetime
interval = str_to_interval(self.datetime)
return interval[1] if isinstance(interval, tuple) else None


class BaseCollectionSearchPostRequest(BaseModel):
"""Collection-Search POST model."""

bbox: Optional[BBox] = None
datetime: Optional[str] = None
bbox: Optional[BBox] = Field(
description="Only return items intersecting this bounding box. Mutually exclusive with **intersects**.", # noqa: E501
json_schema_extra={
"example": [-175.05, -85.05, 175.05, 85.05],
},
)
datetime: Optional[str] = Field(
description="""Only return items that have a temporal property that intersects this value.\n
Either a date-time or an interval, open or closed. Date and time expressions adhere to RFC 3339. Open intervals are expressed using double-dots.""", # noqa: E501
json_schema_extra={
"examples": {
"datetime": {"value": "2018-02-12T23:20:50Z"},
"closed-interval": {"value": "2018-02-12T00:00:00Z/2018-03-18T12:31:12Z"},
"open-interval-from": {"value": "2018-02-12T00:00:00Z/.."},
"open-interval-to": {"value": "../2018-03-18T12:31:12Z"},
},
},
)
limit: Optional[Limit] = Field(
10,
description="Limits the number of results that are included in each page of the response (capped to 10_000).", # noqa: E501
Expand Down
9 changes: 4 additions & 5 deletions stac_fastapi/types/stac_fastapi/types/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from stac_fastapi.types.conformance import BASE_CONFORMANCE_CLASSES
from stac_fastapi.types.extension import ApiExtension
from stac_fastapi.types.requests import get_base_url
from stac_fastapi.types.rfc3339 import DateTimeType
from stac_fastapi.types.search import BaseSearchPostRequest

__all__ = [
Expand Down Expand Up @@ -497,7 +496,7 @@ def get_search(
ids: Optional[List[str]] = None,
bbox: Optional[BBox] = None,
intersects: Optional[Geometry] = None,
datetime: Optional[DateTimeType] = None,
datetime: Optional[str] = None,
limit: Optional[int] = 10,
**kwargs,
) -> stac.ItemCollection:
Expand Down Expand Up @@ -555,7 +554,7 @@ def item_collection(
self,
collection_id: str,
bbox: Optional[BBox] = None,
datetime: Optional[DateTimeType] = None,
datetime: Optional[str] = None,
limit: int = 10,
token: str = None,
**kwargs,
Expand Down Expand Up @@ -733,7 +732,7 @@ async def get_search(
ids: Optional[List[str]] = None,
bbox: Optional[BBox] = None,
intersects: Optional[Geometry] = None,
datetime: Optional[DateTimeType] = None,
datetime: Optional[str] = None,
limit: Optional[int] = 10,
**kwargs,
) -> stac.ItemCollection:
Expand Down Expand Up @@ -791,7 +790,7 @@ async def item_collection(
self,
collection_id: str,
bbox: Optional[BBox] = None,
datetime: Optional[DateTimeType] = None,
datetime: Optional[str] = None,
limit: int = 10,
token: str = None,
**kwargs,
Expand Down
Loading

0 comments on commit 682d429

Please sign in to comment.