Skip to content

Commit 416225e

Browse files
vincentsaragogadomski
authored andcommitted
refactor datetime parsing and validation
1 parent 75304a5 commit 416225e

File tree

5 files changed

+24
-26
lines changed

5 files changed

+24
-26
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ keywords=["stac", "pydantic", "validation"]
1919
authors=[{ name = "Arturo Engineering", email = "[email protected]"}]
2020
license= { text = "MIT" }
2121
requires-python=">=3.8"
22-
dependencies = ["click>=8.1.7", "pydantic>=2.4.1", "geojson-pydantic>=1.0.0"]
22+
dependencies = ["click>=8.1.7", "pydantic>=2.4.1", "geojson-pydantic>=1.0.0", "ciso8601~=2.3"]
2323
dynamic = ["version", "readme"]
2424

2525
[project.scripts]

stac_pydantic/api/search.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from datetime import datetime as dt
22
from typing import Any, Dict, List, Optional, Tuple, Union, cast
33

4+
from ciso8601 import parse_rfc3339
45
from geojson_pydantic.geometries import ( # type: ignore
56
GeometryCollection,
67
LineString,
@@ -16,7 +17,6 @@
1617
from stac_pydantic.api.extensions.query import Operator
1718
from stac_pydantic.api.extensions.sort import SortExtension
1819
from stac_pydantic.shared import BBox
19-
from stac_pydantic.utils import parse_datetime
2020

2121
Intersection = Union[
2222
Point,
@@ -50,16 +50,16 @@ def start_date(self) -> Optional[dt]:
5050
return None
5151
if values[0] == ".." or values[0] == "":
5252
return None
53-
return parse_datetime(values[0])
53+
return parse_rfc3339(values[0])
5454

5555
@property
5656
def end_date(self) -> Optional[dt]:
5757
values = (self.datetime or "").split("/")
5858
if len(values) == 1:
59-
return parse_datetime(values[0])
59+
return parse_rfc3339(values[0])
6060
if values[1] == ".." or values[1] == "":
6161
return None
62-
return parse_datetime(values[1])
62+
return parse_rfc3339(values[1])
6363

6464
# Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-validators for more information.
6565
@model_validator(mode="before")
@@ -109,17 +109,18 @@ def validate_datetime(cls, v: str) -> str:
109109
# Single date is interpreted as end date
110110
values = ["..", v]
111111

112-
dates = []
112+
dates: List[dt] = []
113113
for value in values:
114114
if value == ".." or value == "":
115-
dates.append("..")
116115
continue
117116

118-
parse_datetime(value)
119-
dates.append(value)
117+
dates.append(parse_rfc3339(value))
120118

121-
if ".." not in dates:
122-
if parse_datetime(dates[0]) > parse_datetime(dates[1]):
119+
if len(values) > 2:
120+
raise ValueError("Invalid datetime range, must match format (begin_date, end_date)")
121+
122+
if not {"..", ""}.intersection(set(values)):
123+
if dates[0] > dates[1]:
123124
raise ValueError(
124125
"Invalid datetime range, must match format (begin_date, end_date)"
125126
)

stac_pydantic/item.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from datetime import datetime as dt
22
from typing import Any, Dict, List, Optional, Union
33

4+
from ciso8601 import parse_rfc3339
45
from geojson_pydantic import Feature
56
from pydantic import (
67
AnyUrl,
@@ -19,7 +20,6 @@
1920
StacBaseModel,
2021
StacCommonMetadata,
2122
)
22-
from stac_pydantic.utils import parse_datetime
2323
from stac_pydantic.version import STAC_VERSION
2424

2525

@@ -47,13 +47,13 @@ def validate_datetime(cls, data: Dict[str, Any]) -> Dict[str, Any]:
4747
)
4848

4949
if isinstance(datetime, str):
50-
data["datetime"] = parse_datetime(datetime)
50+
data["datetime"] = parse_rfc3339(datetime)
5151

5252
if isinstance(start_datetime, str):
53-
data["start_datetime"] = parse_datetime(start_datetime)
53+
data["start_datetime"] = parse_rfc3339(start_datetime)
5454

5555
if isinstance(end_datetime, str):
56-
data["end_datetime"] = parse_datetime(end_datetime)
56+
data["end_datetime"] = parse_rfc3339(end_datetime)
5757

5858
return data
5959

stac_pydantic/utils.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
1-
import json
2-
from datetime import datetime
31
from enum import Enum
4-
from typing import Any, Callable, List
5-
6-
from pydantic import TypeAdapter
2+
from typing import Any, List
73

84

95
class AutoValueEnum(Enum):
106
def _generate_next_value_( # type: ignore
117
name: str, start: int, count: int, last_values: List[Any]
128
) -> Any:
139
return name
14-
15-
16-
parse_datetime: Callable[[Any], datetime] = lambda x: TypeAdapter(
17-
datetime
18-
).validate_json(json.dumps(x))

tests/api/test_search.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import time
2-
from datetime import datetime, timezone
2+
from datetime import datetime, timezone, timedelta
33

44
import pytest
55
from pydantic import ValidationError
@@ -93,6 +93,12 @@ def test_invalid_temporal_search():
9393
with pytest.raises(ValidationError):
9494
Search(collections=["collection1"], datetime=utcnow)
9595

96+
t1 = datetime.utcnow()
97+
t2 = t1 + timedelta(seconds=100)
98+
t3 = t2 + timedelta(seconds=100)
99+
with pytest.raises(ValidationError):
100+
Search(collections=["collection1"], datetime=f"{t1.strftime(DATETIME_RFC339)}/{t2.strftime(DATETIME_RFC339)}/{t3.strftime(DATETIME_RFC339)}",)
101+
96102
# End date is before start date
97103
start = datetime.utcnow()
98104
time.sleep(2)

0 commit comments

Comments
 (0)