Skip to content

Commit e09f5de

Browse files
Merge pull request #7 from DACCS-Climate/store-timezone-data
handle multiple timezones
2 parents b255b83 + 264274a commit e09f5de

File tree

5 files changed

+30
-16
lines changed

5 files changed

+30
-16
lines changed

marble_api/versions/v1/data_request/models.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import datetime
12
from collections.abc import Sized
23
from datetime import timezone
3-
from typing import Required, TypedDict
4+
from typing import Required, Self, TypedDict
45

56
from bson import ObjectId
67
from pydantic import (
@@ -14,6 +15,7 @@
1415
ValidationInfo,
1516
field_serializer,
1617
field_validator,
18+
model_validator,
1719
)
1820
from pydantic.functional_validators import BeforeValidator
1921
from pydantic.json_schema import SkipJsonSchema
@@ -55,6 +57,7 @@ class DataRequest(BaseModel):
5557
authors: list[Author]
5658
geometry: GeoJSON | None
5759
temporal: Temporal
60+
tz_offset: SkipJsonSchema[list[float] | None] = Field(default=None, exclude=True)
5861
links: Links
5962
path: str
6063
contact: EmailStr
@@ -78,6 +81,21 @@ def validate_geometries(cls, value: GeoJSON | None) -> dict | None:
7881
validate_collapsible(value)
7982
return value
8083

84+
@model_validator(mode="after")
85+
def get_tz_offset(self) -> Self:
86+
"""Store the timezone offset for the temporal data."""
87+
if self.temporal is not None:
88+
self.tz_offset = [datetime.datetime.utcoffset(t).total_seconds() for t in self.temporal]
89+
return self
90+
91+
@field_serializer("temporal")
92+
def convert_from_utc(self, value: Temporal, info: FieldSerializationInfo) -> list[str]:
93+
"""Apply the timezone offset to convert this from UTC to a date in the correct timezone."""
94+
return [
95+
t.astimezone(datetime.timezone(datetime.timedelta(seconds=self.tz_offset[i]))).isoformat()
96+
for i, t in enumerate(value)
97+
]
98+
8199
@field_serializer("user")
82100
def require_user_set(self, value: str, info: FieldSerializationInfo) -> str:
83101
"""Require that the user_name is set when the model is serialized."""

marble_api/versions/v1/data_request/routes.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ async def patch_data_request(
6464
data_request.user = user
6565
selector = {"_id": _data_request_id(request_id)}
6666
if updated_fields:
67-
updated_fields.update(data_request.model_dump(include="stac_item"))
6867
result = await client.db["data-request"].find_one_and_update(
6968
selector, {"$set": updated_fields}, return_document=ReturnDocument.AFTER
7069
)

test/faker_providers.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import datetime
2-
31
import bson
42
import pytest
53
from faker import Faker
@@ -192,22 +190,22 @@ def author(self):
192190
author_["email"] = self.generator.email()
193191
return author_
194192

195-
def utc_date_time_seconds_precision(self):
196-
return self.generator.date_time(tzinfo=datetime.timezone.utc).replace(microsecond=0)
193+
def tz_aware_date_time_seconds_precision(self):
194+
return self.generator.date_time(tzinfo=self.generator.pytimezone()).replace(microsecond=0)
197195

198196
def temporal(self):
199197
opt = self.generator.random.random()
200198
if opt < 1 / 3:
201199
return sorted(
202200
[
203-
self.utc_date_time_seconds_precision(),
204-
self.utc_date_time_seconds_precision(),
201+
self.tz_aware_date_time_seconds_precision(),
202+
self.tz_aware_date_time_seconds_precision(),
205203
]
206204
)
207205
elif opt < 2 / 3:
208-
return [self.utc_date_time_seconds_precision()] * 2
206+
return [self.tz_aware_date_time_seconds_precision()] * 2
209207
else:
210-
return [self.utc_date_time_seconds_precision()]
208+
return [self.tz_aware_date_time_seconds_precision()]
211209

212210
def link(self):
213211
return {"href": self.generator.uri(), "rel": self.generator.word(), "type": self.generator.mime_type()}

test/integration/versions/v1/data_request/test_routes.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from stac_pydantic import Item
88

99
from marble_api.database import client
10-
from marble_api.versions.v1.data_request.models import DataRequest, DataRequestPublic
10+
from marble_api.versions.v1.data_request.models import DataRequestPublic
1111
from marble_api.versions.v1.data_request.routes import get_data_requests
1212

1313
pytestmark = pytest.mark.anyio
@@ -226,11 +226,10 @@ async def test_valid(self, fake, async_client, collection_route, data_requests):
226226
data = fake.data_request().model_dump_json(exclude=["user"])
227227
response = await async_client.post(collection_route, json=json.loads(data))
228228
assert response.status_code == 200
229-
assert (id_ := response.json().get("id"))
229+
response_data = response.json()
230+
assert (id_ := response_data.pop("id", None))
230231
bson.ObjectId(id_) # check that the id is a valid object id
231-
assert {"user": data_requests[0]["user"], **json.loads(data)} == json.loads(
232-
DataRequest(**response.json()).model_dump_json()
233-
)
232+
assert {"user": data_requests[0]["user"], **json.loads(data)} == response_data
234233

235234
async def test_invalid_authors(self, fake, async_client, collection_route):
236235
data = json.loads(fake.data_request().model_dump_json())

test/unit/versions/v1/data_request/test_models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def test_temporal_to_utc(self, fake_class):
137137
offset = datetime.timezone(datetime.timedelta(hours=3))
138138
temporal = [now, now + datetime.timedelta(hours=1)]
139139
temporal_offset = [t.astimezone(offset) for t in temporal]
140-
request = fake_class(temporal=temporal_offset)
140+
request = fake_class(temporal=[t.isoformat() for t in temporal_offset])
141141
assert (
142142
request.stac_item["properties"]["datetime"]
143143
== temporal[0].isoformat()

0 commit comments

Comments
 (0)