Skip to content

Commit bb50349

Browse files
committed
Issue #797 STACAPIJobDatabase.item_from() use "datetime" from series instead of "now"
eliminate some fixture anti-patterns in tests too
1 parent 4dfdf77 commit bb50349

File tree

3 files changed

+107
-67
lines changed

3 files changed

+107
-67
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515

1616
### Fixed
1717

18+
- `STACAPIJobDatabase.item_from()` use "datetime" from series instead of always taking "now" ([#797](https://github.com/Open-EO/openeo-python-client/issues/797))
19+
1820

1921
## [0.43.0] - 2025-07-02
2022

openeo/extra/job_management/stac_job_db.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,10 @@ def item_from(self, series: pd.Series) -> pystac.Item:
136136
item_dict.setdefault("links", [])
137137
item_dict.setdefault("properties", series_dict)
138138

139-
dt = series_dict.get("datetime", None)
140-
if dt and item_dict["properties"].get("datetime", None) is None:
141-
dt_str = pystac.utils.datetime_to_str(dt) if isinstance(dt, datetime.datetime) else dt
142-
item_dict["properties"]["datetime"] = dt_str
143-
144-
else:
145-
item_dict["properties"]["datetime"] = pystac.utils.datetime_to_str(datetime.datetime.now())
139+
dt = item_dict["properties"].get("datetime", datetime.datetime.now(tz=datetime.timezone.utc))
140+
if isinstance(dt, datetime.datetime):
141+
dt = pystac.utils.datetime_to_str(dt)
142+
item_dict["properties"]["datetime"] = dt
146143

147144
if self.has_geometry:
148145
item_dict["geometry"] = series[self.geometry_column]

tests/extra/job_management/test_stac_job_db.py

Lines changed: 101 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import datetime
22
import re
3-
from typing import Any, Dict
3+
from typing import Any, Dict, Optional, Union
44
from unittest import mock
55
from unittest.mock import MagicMock, patch
66

@@ -150,68 +150,44 @@ def normalized_dummy_geodataframe() -> pd.DataFrame:
150150
)
151151

152152

153-
FAKE_NOW = datetime.datetime(2020, 5, 22)
153+
def _pystac_item(
154+
*,
155+
id: str,
156+
properties: Optional[dict] = None,
157+
geometry: Optional = None,
158+
bbox: Optional = None,
159+
datetime_: Union[None, str, datetime.date, datetime.date] = "2025-06-07",
160+
) -> pystac.Item:
161+
"""Helper to easily construct a dummy but valid pystac.Item"""
162+
if isinstance(datetime_, str):
163+
datetime_ = pystac.utils.str_to_datetime(datetime_)
164+
elif isinstance(datetime_, datetime.date):
165+
datetime_ = datetime.datetime.combine(datetime_, datetime.time.min, tzinfo=datetime.timezone.utc)
154166

155-
156-
@pytest.fixture
157-
def dummy_stac_item() -> pystac.Item:
158-
properties = {
159-
"datetime": pystac.utils.datetime_to_str(FAKE_NOW),
160-
"some_property": "value",
161-
}
162-
163-
return pystac.Item(id="test", geometry=None, bbox=None, properties=properties, datetime=FAKE_NOW)
167+
return pystac.Item(
168+
id=id,
169+
geometry=geometry,
170+
bbox=bbox,
171+
properties=properties or {},
172+
datetime=datetime_,
173+
)
164174

165175

166176
@pytest.fixture
167-
def dummy_stac_item_geometry() -> pystac.Item:
177+
def dummy_stac_item() -> pystac.Item:
168178
properties = {
169-
"datetime": pystac.utils.datetime_to_str(FAKE_NOW),
179+
"datetime": "2020-05-22T00:00:00Z",
170180
"some_property": "value",
171-
"geometry": {"type": "Polygon", "coordinates": (((0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0), (0.0, 0.0)),)},
172181
}
173-
174182
return pystac.Item(
175-
id="test",
176-
geometry={"type": "Polygon", "coordinates": (((0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0), (0.0, 0.0)),)},
177-
bbox=(0.0, 0.0, 1.0, 1.0),
178-
properties=properties,
179-
datetime=FAKE_NOW,
180-
)
181-
182-
183-
@pytest.fixture
184-
def dummy_series() -> pd.Series:
185-
return pd.Series(
186-
{"item_id": "test", "datetime": pystac.utils.datetime_to_str(FAKE_NOW), "some_property": "value"}, name="test"
183+
id="test", geometry=None, bbox=None, properties=properties, datetime=datetime.datetime(2020, 5, 22)
187184
)
188185

189186

190187
@pytest.fixture
191188
def dummy_series_no_item_id() -> pd.Series:
192-
return pd.Series({"datetime": pystac.utils.datetime_to_str(FAKE_NOW), "some_property": "value"}, name="test")
189+
return pd.Series({"datetime": "2020-05-22T00:00:00Z", "some_property": "value"}, name="test")
193190

194-
@pytest.fixture
195-
def dummy_series_geometry() -> pd.Series:
196-
return pd.Series(
197-
{
198-
"item_id": "test",
199-
"datetime": pystac.utils.datetime_to_str(FAKE_NOW),
200-
"some_property": "value",
201-
"geometry": {
202-
"type": "Polygon",
203-
"coordinates": (((0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0), (0.0, 0.0)),),
204-
},
205-
},
206-
name="test",
207-
)
208-
209-
210-
@pytest.fixture
211-
def patch_datetime_now():
212-
with patch("datetime.datetime") as mock_datetime:
213-
mock_datetime.now.return_value = FAKE_NOW
214-
yield mock_datetime
215191

216192

217193
@pytest.fixture
@@ -220,6 +196,7 @@ def bulk_dataframe():
220196
{
221197
"item_id": [f"test-{i}" for i in range(10)],
222198
"some_property": [f"value-{i}" for i in range(10)],
199+
"datetime": [f"2020-{i+1:02d}-01" for i in range(10)],
223200
},
224201
index=[i for i in range(10)],
225202
)
@@ -278,16 +255,80 @@ def test_initialize_from_df_with_geometry(
278255
def test_series_from(self, job_db_exists, dummy_series_no_item_id, dummy_stac_item):
279256
pdt.assert_series_equal(job_db_exists.series_from(dummy_stac_item), dummy_series_no_item_id)
280257

281-
def test_item_from(self, patch_datetime_now, job_db_exists, dummy_series, dummy_stac_item):
282-
item = job_db_exists.item_from(dummy_series)
283-
assert item.to_dict() == dummy_stac_item.to_dict()
284-
285-
def test_item_from_geometry(
286-
self, patch_datetime_now, job_db_exists, dummy_series_geometry, dummy_stac_item_geometry
287-
):
258+
@pytest.mark.parametrize(
259+
["series", "expected"],
260+
[
261+
# Minimal (using current time as datetime)
262+
(
263+
pd.Series({"item_id": "item-123"}),
264+
_pystac_item(id="item-123", datetime_="2022-02-02"),
265+
),
266+
# With explicit datetime, and some other properties
267+
(
268+
pd.Series(
269+
{
270+
"item_id": "item-123",
271+
"datetime": "2023-04-05",
272+
"hello": "world",
273+
}
274+
),
275+
_pystac_item(
276+
id="item-123",
277+
properties={"hello": "world"},
278+
datetime_="2023-04-05",
279+
),
280+
),
281+
(
282+
pd.Series(
283+
{
284+
"item_id": "item-123",
285+
"datetime": datetime.datetime(2023, 4, 5, 12, 34),
286+
"hello": "world",
287+
}
288+
),
289+
_pystac_item(
290+
id="item-123",
291+
properties={"hello": "world"},
292+
datetime_="2023-04-05T12:34:00Z",
293+
),
294+
),
295+
],
296+
)
297+
def test_item_from(self, job_db_exists, series, expected, time_machine):
298+
time_machine.move_to("2022-02-02T00:00:00Z")
299+
item = job_db_exists.item_from(series)
300+
assert isinstance(item, pystac.Item)
301+
assert item.to_dict() == expected.to_dict()
302+
303+
@pytest.mark.parametrize(
304+
["series", "expected"],
305+
[
306+
(
307+
pd.Series(
308+
{
309+
"item_id": "item-123",
310+
"datetime": "2023-04-05",
311+
"geometry": {"type": "Polygon", "coordinates": (((1, 2), (3, 4), (0, 5), (1, 2)),)},
312+
},
313+
),
314+
_pystac_item(
315+
id="item-123",
316+
datetime_="2023-04-05",
317+
geometry={"type": "Polygon", "coordinates": (((1, 2), (3, 4), (0, 5), (1, 2)),)},
318+
bbox=(0.0, 2.0, 3.0, 5.0),
319+
properties={
320+
"geometry": {"type": "Polygon", "coordinates": (((1, 2), (3, 4), (0, 5), (1, 2)),)},
321+
},
322+
),
323+
),
324+
],
325+
)
326+
def test_item_from_geometry(self, job_db_exists, series, expected):
327+
# TODO avoid this `has_geometry` hack?
288328
job_db_exists.has_geometry = True
289-
item = job_db_exists.item_from(dummy_series_geometry)
290-
assert item.to_dict() == dummy_stac_item_geometry.to_dict()
329+
item = job_db_exists.item_from(series)
330+
assert isinstance(item, pystac.Item)
331+
assert item.to_dict() == expected.to_dict()
291332

292333
@patch("openeo.extra.job_management.stac_job_db.STACAPIJobDatabase.get_by_status")
293334
def test_count_by_status(self, mock_get_by_status, normalized_dummy_dataframe, job_db_exists):
@@ -315,15 +356,15 @@ def test_get_by_status_result(self, job_db_exists):
315356
pd.DataFrame(
316357
{
317358
"item_id": ["test"],
318-
"datetime": [pystac.utils.datetime_to_str(FAKE_NOW)],
359+
"datetime": ["2020-05-22T00:00:00Z"],
319360
"some_property": ["value"],
320361
},
321362
index=[0],
322363
),
323364
)
324365

325366
@patch("requests.post")
326-
def test_persist_single_chunk(self, mock_requests_post, bulk_dataframe, job_db_exists, patch_datetime_now):
367+
def test_persist_single_chunk(self, mock_requests_post, bulk_dataframe, job_db_exists):
327368
def bulk_items(df):
328369
all_items = []
329370
if not df.empty:

0 commit comments

Comments
 (0)