Skip to content

Commit e3c138c

Browse files
keep datetime as string type in GET Request models (#780)
* keep datetime as string type in GET Request models * fix * use mixin * reorder * add parse_datetime
1 parent 6d65525 commit e3c138c

File tree

8 files changed

+132
-56
lines changed

8 files changed

+132
-56
lines changed

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## [Unreleased]
44

5+
## Changed
6+
7+
* use `string` type instead of python `datetime.datetime` for datetime parameter in `BaseSearchGetRequest`, `ItemCollectionUri` and `BaseCollectionSearchGetRequest` GET models
8+
59
## [3.0.5] - 2025-01-10
610

711
### Removed

stac_fastapi/api/stac_fastapi/api/models.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@
99
from typing_extensions import Annotated
1010

1111
from stac_fastapi.types.extension import ApiExtension
12-
from stac_fastapi.types.rfc3339 import DateTimeType
1312
from stac_fastapi.types.search import (
1413
APIRequest,
1514
BaseSearchGetRequest,
1615
BaseSearchPostRequest,
16+
DatetimeMixin,
17+
DateTimeQueryType,
1718
Limit,
1819
_bbox_converter,
19-
_datetime_converter,
20+
_validate_datetime,
2021
)
2122

2223
try:
@@ -110,7 +111,7 @@ class EmptyRequest(APIRequest):
110111

111112

112113
@attr.s
113-
class ItemCollectionUri(APIRequest):
114+
class ItemCollectionUri(APIRequest, DatetimeMixin):
114115
"""Get item collection."""
115116

116117
collection_id: Annotated[str, Path(description="Collection ID")] = attr.ib()
@@ -121,9 +122,7 @@ class ItemCollectionUri(APIRequest):
121122
),
122123
] = attr.ib(default=10)
123124
bbox: Optional[BBox] = attr.ib(default=None, converter=_bbox_converter)
124-
datetime: Optional[DateTimeType] = attr.ib(
125-
default=None, converter=_datetime_converter
126-
)
125+
datetime: DateTimeQueryType = attr.ib(default=None, validator=_validate_datetime)
127126

128127

129128
class GeoJSONResponse(JSONResponse):

stac_fastapi/api/tests/test_app.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from datetime import datetime
21
from typing import List, Optional, Union
32

43
import attr
@@ -141,7 +140,7 @@ def get_search(
141140
ids: Optional[List[str]] = None,
142141
bbox: Optional[List[NumType]] = None,
143142
intersects: Optional[str] = None,
144-
datetime: Optional[Union[str, datetime]] = None,
143+
datetime: Optional[str] = None,
145144
limit: Optional[int] = 10,
146145
filter: Optional[str] = None,
147146
filter_crs: Optional[str] = None,
@@ -221,7 +220,7 @@ def get_search(
221220
ids: Optional[List[str]] = None,
222221
bbox: Optional[List[NumType]] = None,
223222
intersects: Optional[str] = None,
224-
datetime: Optional[Union[str, datetime]] = None,
223+
datetime: Optional[str] = None,
225224
limit: Optional[int] = 10,
226225
**kwargs,
227226
) -> stac.ItemCollection:
@@ -247,7 +246,7 @@ def item_collection(
247246
self,
248247
collection_id: str,
249248
bbox: Optional[List[Union[float, int]]] = None,
250-
datetime: Optional[Union[str, datetime]] = None,
249+
datetime: Optional[str] = None,
251250
limit: int = 10,
252251
token: str = None,
253252
**kwargs,
@@ -392,6 +391,7 @@ def test_client_datetime_input_params():
392391

393392
class FakeClient(BaseCoreClient):
394393
def post_search(self, search_request: BaseSearchPostRequest, **kwargs):
394+
assert isinstance(search_request.datetime, str)
395395
return search_request.datetime
396396

397397
def get_search(
@@ -400,10 +400,11 @@ def get_search(
400400
ids: Optional[List[str]] = None,
401401
bbox: Optional[List[NumType]] = None,
402402
intersects: Optional[str] = None,
403-
datetime: Optional[Union[str, datetime]] = None,
403+
datetime: Optional[str] = None,
404404
limit: Optional[int] = 10,
405405
**kwargs,
406406
):
407+
assert isinstance(datetime, str)
407408
return datetime
408409

409410
def get_item(self, item_id: str, collection_id: str, **kwargs) -> stac.Item:
@@ -419,7 +420,7 @@ def item_collection(
419420
self,
420421
collection_id: str,
421422
bbox: Optional[List[Union[float, int]]] = None,
422-
datetime: Optional[Union[str, datetime]] = None,
423+
datetime: Optional[str] = None,
423424
limit: int = 10,
424425
token: str = None,
425426
**kwargs,
@@ -439,16 +440,34 @@ def item_collection(
439440
"datetime": "2020-01-01T00:00:00.00001Z",
440441
},
441442
)
443+
get_search_zero = client.get(
444+
"/search",
445+
params={
446+
"collections": ["test"],
447+
"datetime": "2020-01-01T00:00:00.0000Z",
448+
},
449+
)
442450
post_search = client.post(
443451
"/search",
444452
json={
445453
"collections": ["test"],
446454
"datetime": "2020-01-01T00:00:00.00001Z",
447455
},
448456
)
457+
post_search_zero = client.post(
458+
"/search",
459+
json={
460+
"collections": ["test"],
461+
"datetime": "2020-01-01T00:00:00.0000Z",
462+
},
463+
)
449464

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

453470
assert post_search.status_code == 200, post_search.text
454471
assert post_search.json() == "2020-01-01T00:00:00.00001Z"
472+
assert post_search_zero.status_code == 200, post_search_zero.text
473+
assert post_search_zero.json() == "2020-01-01T00:00:00.0000Z"

stac_fastapi/api/tests/test_models.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,24 @@ def test_create_get_request_model():
3535

3636
assert model.collections == ["test1", "test2"]
3737
assert model.filter_crs == "epsg:4326"
38-
d = model.datetime
38+
d = model.start_date
3939
assert d.microsecond == 10
40+
assert not model.end_date
4041

42+
model = request_model(
43+
datetime="2020-01-01T00:00:00.00001Z/2020-01-02T00:00:00.00001Z",
44+
)
45+
assert model.start_date
46+
assert model.end_date
47+
48+
# invalid datetime format
4149
with pytest.raises(HTTPException):
4250
request_model(datetime="yo")
4351

52+
# Wrong order
53+
with pytest.raises(HTTPException):
54+
request_model(datetime="2020-01-02T00:00:00.00001Z/2020-01-01T00:00:00.00001Z")
55+
4456
app = FastAPI()
4557

4658
@app.get("/test")
@@ -92,6 +104,9 @@ def test_create_post_request_model(filter_val, passes):
92104
assert model.filter == filter_val
93105
assert model.datetime == "2020-01-01T00:00:00.00001Z"
94106

107+
with pytest.raises(ValidationError):
108+
request_model(datetime="yo")
109+
95110

96111
@pytest.mark.parametrize(
97112
"sortby,passes",

stac_fastapi/extensions/stac_fastapi/extensions/core/collection_search/request.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,22 @@
1010
from stac_pydantic.shared import BBox
1111
from typing_extensions import Annotated
1212

13-
from stac_fastapi.types.rfc3339 import DateTimeType
1413
from stac_fastapi.types.search import (
1514
APIRequest,
15+
DatetimeMixin,
16+
DateTimeQueryType,
1617
Limit,
1718
_bbox_converter,
18-
_datetime_converter,
19+
_validate_datetime,
1920
)
2021

2122

2223
@attr.s
23-
class BaseCollectionSearchGetRequest(APIRequest):
24+
class BaseCollectionSearchGetRequest(APIRequest, DatetimeMixin):
2425
"""Basics additional Collection-Search parameters for the GET request."""
2526

2627
bbox: Optional[BBox] = attr.ib(default=None, converter=_bbox_converter)
27-
datetime: Optional[DateTimeType] = attr.ib(
28-
default=None, converter=_datetime_converter
29-
)
28+
datetime: DateTimeQueryType = attr.ib(default=None, validator=_validate_datetime)
3029
limit: Annotated[
3130
Optional[Limit],
3231
Query(
@@ -38,8 +37,26 @@ class BaseCollectionSearchGetRequest(APIRequest):
3837
class BaseCollectionSearchPostRequest(BaseModel):
3938
"""Collection-Search POST model."""
4039

41-
bbox: Optional[BBox] = None
42-
datetime: Optional[str] = None
40+
bbox: Optional[BBox] = Field(
41+
default=None,
42+
description="Only return items intersecting this bounding box. Mutually exclusive with **intersects**.", # noqa: E501
43+
json_schema_extra={
44+
"example": [-175.05, -85.05, 175.05, 85.05],
45+
},
46+
)
47+
datetime: Optional[str] = Field(
48+
default=None,
49+
description="""Only return items that have a temporal property that intersects this value.\n
50+
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
51+
json_schema_extra={
52+
"examples": {
53+
"datetime": {"value": "2018-02-12T23:20:50Z"},
54+
"closed-interval": {"value": "2018-02-12T00:00:00Z/2018-03-18T12:31:12Z"},
55+
"open-interval-from": {"value": "2018-02-12T00:00:00Z/.."},
56+
"open-interval-to": {"value": "../2018-03-18T12:31:12Z"},
57+
},
58+
},
59+
)
4360
limit: Optional[Limit] = Field(
4461
10,
4562
description="Limits the number of results that are included in each page of the response (capped to 10_000).", # noqa: E501

stac_fastapi/extensions/tests/test_collection_search.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,7 @@ def test_collection_search_extension_default():
120120
assert response.is_success, response.json()
121121
response_dict = response.json()
122122
assert [-175.05, -85.05, 175.05, 85.05] == response_dict["bbox"]
123-
assert [
124-
"2020-06-13T13:00:00+00:00",
125-
"2020-06-13T14:00:00+00:00",
126-
] == response_dict["datetime"]
123+
assert "2020-06-13T13:00:00Z/2020-06-13T14:00:00Z" == response_dict["datetime"]
127124
assert 100 == response_dict["limit"]
128125

129126

@@ -211,10 +208,7 @@ def test_collection_search_extension_models():
211208
assert response.is_success, response.json()
212209
response_dict = response.json()
213210
assert [-175.05, -85.05, 175.05, 85.05] == response_dict["bbox"]
214-
assert [
215-
"2020-06-13T13:00:00+00:00",
216-
"2020-06-13T14:00:00+00:00",
217-
] == response_dict["datetime"]
211+
assert "2020-06-13T13:00:00Z/2020-06-13T14:00:00Z" == response_dict["datetime"]
218212
assert 100 == response_dict["limit"]
219213
assert ["EO", "Earth Observation"] == response_dict["q"]
220214
assert "id='item_id' AND collection='collection_id'" == response_dict["filter"]

stac_fastapi/types/stac_fastapi/types/core.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
from stac_fastapi.types.conformance import BASE_CONFORMANCE_CLASSES
2020
from stac_fastapi.types.extension import ApiExtension
2121
from stac_fastapi.types.requests import get_base_url
22-
from stac_fastapi.types.rfc3339 import DateTimeType
2322
from stac_fastapi.types.search import BaseSearchPostRequest
2423

2524
__all__ = [
@@ -497,7 +496,7 @@ def get_search(
497496
ids: Optional[List[str]] = None,
498497
bbox: Optional[BBox] = None,
499498
intersects: Optional[Geometry] = None,
500-
datetime: Optional[DateTimeType] = None,
499+
datetime: Optional[str] = None,
501500
limit: Optional[int] = 10,
502501
**kwargs,
503502
) -> stac.ItemCollection:
@@ -555,7 +554,7 @@ def item_collection(
555554
self,
556555
collection_id: str,
557556
bbox: Optional[BBox] = None,
558-
datetime: Optional[DateTimeType] = None,
557+
datetime: Optional[str] = None,
559558
limit: int = 10,
560559
token: str = None,
561560
**kwargs,
@@ -733,7 +732,7 @@ async def get_search(
733732
ids: Optional[List[str]] = None,
734733
bbox: Optional[BBox] = None,
735734
intersects: Optional[Geometry] = None,
736-
datetime: Optional[DateTimeType] = None,
735+
datetime: Optional[str] = None,
737736
limit: Optional[int] = 10,
738737
**kwargs,
739738
) -> stac.ItemCollection:
@@ -791,7 +790,7 @@ async def item_collection(
791790
self,
792791
collection_id: str,
793792
bbox: Optional[BBox] = None,
794-
datetime: Optional[DateTimeType] = None,
793+
datetime: Optional[str] = None,
795794
limit: int = 10,
796795
token: str = None,
797796
**kwargs,

0 commit comments

Comments
 (0)