Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

## Unreleased

- Add datetime validation for collection's time intervals (Must follow [`RFC 3339, section 5.6.`](https://datatracker.ietf.org/doc/html/rfc3339#section-5.6))

## 3.2.0 (2025-03-20)

- Move `validate_bbox` and `validate_datetime` field validation functions outside the Search class (to enable re-utilization)
Expand Down
26 changes: 26 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Contributing

Issues and pull requests are more than welcome.

**dev install**

```bash
git clone https://github.com/stac-utils/stac-pydantic.git
cd stac-pydantic
python -m pip install -e ".[dev]"
```

You can then run the tests with the following command:

```sh
python -m pytest --cov stac_pydantic --cov-report term-missing
```


**pre-commit**

This repo is set to use `pre-commit` to run *ruff*, *pydocstring* and mypy when committing new code.

```bash
pre-commit install
```
4 changes: 2 additions & 2 deletions stac_pydantic/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pydantic import Field

from stac_pydantic.catalog import _Catalog
from stac_pydantic.shared import Asset, NumType, Provider, StacBaseModel
from stac_pydantic.shared import Asset, NumType, Provider, StacBaseModel, UtcDatetime


class SpatialExtent(StacBaseModel):
Expand All @@ -19,7 +19,7 @@ class TimeInterval(StacBaseModel):
https://github.com/radiantearth/stac-spec/blob/v1.0.0/collection-spec/collection-spec.md#temporal-extent-object
"""

interval: List[List[Union[str, None]]]
interval: List[List[Union[UtcDatetime, None]]]


class Extent(StacBaseModel):
Expand Down
4 changes: 2 additions & 2 deletions tests/api/examples/v1.0.0/example-collection-list.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@
"temporal":{
"interval":[
[
"2000-03-04T12:00:00.000000Z",
"2006-12-31T12:00:00.000000Z"
"2000-03-04T12:00:00Z",
"2006-12-31T12:00:00Z"
]
]
}
Expand Down
2 changes: 1 addition & 1 deletion tests/api/test_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@

def test_collection_list():
test_collection_list = request(EXAMPLE_COLLECTION_LIST, PATH)
valid_collection_list = Collections(**test_collection_list).model_dump()
valid_collection_list = Collections(**test_collection_list).model_dump(mode="json")
dict_match(test_collection_list, valid_collection_list)
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def dict_match(d1: dict, d2: dict):
# to compare the values as datetime objects.
elif "datetime" in diff[1]:
dates = [
UtcDatetimeAdapter.validate_strings(date)
UtcDatetimeAdapter.validate_strings(date, strict=True)
if isinstance(date, str)
else date
for date in diff[2]
Expand Down
40 changes: 37 additions & 3 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from shapely.geometry import shape

from stac_pydantic import Collection, Item, ItemProperties
from stac_pydantic.collection import TimeInterval
from stac_pydantic.extensions import _fetch_and_cache_schema, validate_extensions
from stac_pydantic.links import Link, Links
from stac_pydantic.shared import MimeTypes, StacCommonMetadata
Expand Down Expand Up @@ -81,13 +82,13 @@ def test_version_extension_item() -> None:

def test_version_extension_collection() -> None:
test_coll = request(VERSION_EXTENSION_COLLECTION)
valid_coll = Collection(**test_coll).model_dump()
valid_coll = Collection(**test_coll).model_dump(mode="json")
dict_match(test_coll, valid_coll)


def test_item_assets_extension() -> None:
test_coll = request(ITEM_ASSET_EXTENSION)
valid_coll = Collection(**test_coll).model_dump()
valid_coll = Collection(**test_coll).model_dump(mode="json")
dict_match(test_coll, valid_coll)


Expand Down Expand Up @@ -139,7 +140,9 @@ def test_extension_validation_schema_cache() -> None:
def test_to_json(infile, model):
test_item = request(infile)
validated = model(**test_item)
dict_match(json.loads(validated.model_dump_json()), validated.model_dump())
dict_match(
json.loads(validated.model_dump_json()), validated.model_dump(mode="json")
)


def test_item_to_json() -> None:
Expand Down Expand Up @@ -345,3 +348,34 @@ def test_item_bbox_validation() -> None:
test_item["bbox"] = None
with pytest.raises(ValueError, match="bbox is required if geometry is not null"):
Item(**test_item)


@pytest.mark.parametrize(
"interval",
[
[[None, "yo"]],
[["yo", None]],
[["yo", "yo"]],
],
)
def test_time_intervals_invalid(interval) -> None:
"""Check Time Interval model."""
with pytest.raises(ValidationError):
TimeInterval(interval=interval)


@pytest.mark.parametrize(
"interval",
[
[["2024-01-01T00:00:00Z", None]],
[[None, "2024-01-01T00:00:00Z"]],
[["2023-01-01T00:00:00Z", "2024-01-01T00:00:00Z"]],
[
["2023-01-01T00:00:00Z", "2024-01-01T00:00:00Z"],
["2023-01-31T00:00:00Z", "2024-01-01T00:00:00Z"],
],
],
)
def test_time_intervals_valid(interval) -> None:
"""Check Time Interval model."""
assert TimeInterval(interval=interval)