Skip to content

Commit c860606

Browse files
committed
Eliminate deprecated utcnow usage patterns
and get rid of old time mocking hacks see Open-EO/openeo-python-driver#389
1 parent e39d30e commit c860606

14 files changed

+97
-140
lines changed

openeogeotrellis/backend.py

+17-12
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
_extract_load_parameters, ENV_MAX_BUFFER
6565
from openeo_driver.save_result import ImageCollectionResult
6666
from openeo_driver.users import User
67+
from openeo_driver.util.date_math import now_utc
6768
from openeo_driver.util.geometry import BoundingBox
6869
from openeo_driver.util.http import requests_with_retry
6970
from openeo_driver.util.utm import area_in_square_meters
@@ -220,18 +221,22 @@ def _create_service(self, user_id: str, process_graph: dict, service_type: str,
220221
logger.info("Can not create service for: " + str(image_collection))
221222
raise OpenEOApiException("Can not create service for: " + str(image_collection))
222223

223-
224-
wmts_base_url = os.getenv('WMTS_BASE_URL_PATTERN', 'http://openeo.vgt.vito.be/openeo/services/%s') % service_id
225-
226-
self.service_registry.persist(user_id, ServiceMetadata(
227-
id=service_id,
228-
process={"process_graph": process_graph},
229-
url=wmts_base_url + "/service/wmts",
230-
type=service_type,
231-
enabled=True,
232-
attributes={},
233-
configuration=configuration,
234-
created=dt.datetime.utcnow()), api_version)
224+
wmts_base_url = os.getenv("WMTS_BASE_URL_PATTERN", "http://openeo.vgt.vito.be/openeo/services/%s") % service_id
225+
226+
self.service_registry.persist(
227+
user_id,
228+
ServiceMetadata(
229+
id=service_id,
230+
process={"process_graph": process_graph},
231+
url=wmts_base_url + "/service/wmts",
232+
type=service_type,
233+
enabled=True,
234+
attributes={},
235+
configuration=configuration,
236+
created=now_utc(),
237+
),
238+
api_version,
239+
)
235240

236241
secondary_service = self._wmts_service(image_collection, configuration, wmts_base_url)
237242

openeogeotrellis/config/s3_config.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
from __future__ import annotations
2+
23
import re
34
from configparser import ConfigParser
45
from io import StringIO
56
import os
67
from dataclasses import dataclass
78
from typing import Optional
9+
import time
810

911
from openeogeotrellis.config import get_backend_config
10-
from openeogeotrellis.utils import utcnow_epoch
1112
from openeogeotrellis.workspace import ObjectStorageWorkspace
1213

1314

@@ -117,5 +118,5 @@ def _sanitize_session_name(session_name: str) -> str:
117118
"""
118119
sanitized = re.sub(r"[^\w+=,.@-]", "", session_name)
119120
if len(sanitized) < 2:
120-
sanitized += str(f"-{utcnow_epoch()}")
121+
sanitized += str(f"-{time.time()}")
121122
return sanitized[0:64]

openeogeotrellis/deploy/batch_job_cfg_secret.yaml.j2

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ metadata:
66
labels:
77
job_id: {{ job_id }}
88
annotations:
9-
created_at: "{{ utcnow_epoch() }}"
9+
created_at: "{{ unix_time() }}"
1010
data:
1111
profile_file: {{ profile_file_content | b64encode }}
1212
token: {{ token | b64encode }}
13-
type: Opaque
13+
type: Opaque

openeogeotrellis/integrations/identity.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010

1111
import jwt
1212

13+
from openeo_driver.util.date_math import now_utc
1314
from openeogeotrellis.config import get_backend_config
14-
from openeogeotrellis.utils import FileChangeWatcher, utcnow
15+
from openeogeotrellis.utils import FileChangeWatcher
1516

1617
logger = logging.getLogger(__name__)
1718

@@ -99,7 +100,7 @@ def get_job_token(self, sub_id: str, user_id: str, job_id: str) -> Optional[str]
99100
return None
100101
else:
101102
idp_details = self._IDP_DETAILS
102-
now = utcnow()
103+
now = now_utc()
103104
return jwt.encode(
104105
{
105106
"sub": sub_id,

openeogeotrellis/integrations/kubernetes.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
import logging
66
import os
77
import pkg_resources
8+
import time
89

910
from jinja2 import Environment, FileSystemLoader
11+
1012
from openeo_driver.jobregistry import JOB_STATUS
1113
from openeo_driver.utils import generate_unique_id
1214

13-
from openeogeotrellis.utils import utcnow_epoch
1415

1516
_log = logging.getLogger(__name__)
1617

@@ -97,8 +98,9 @@ def base64encode(input_str: str) -> str:
9798
input_str = str(input_str)
9899
return base64.b64encode(input_str.encode("utf-8")).decode("utf-8")
99100

100-
jinja_env.filters['b64encode'] = base64encode
101-
jinja_env.globals['utcnow_epoch'] = utcnow_epoch
101+
jinja_env.filters["b64encode"] = base64encode
102+
jinja_env.globals["utcnow_epoch"] = time.time # TODO: remove this deprecated/unused utility
103+
jinja_env.globals["unix_time"] = time.time
102104
jinja_template = jinja_env.from_string(open(jinja_path).read())
103105

104106
rendered = jinja_template.render(**kwargs)

openeogeotrellis/job_registry.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ def register(
100100
'api_version': api_version,
101101
"specification": specification_blob,
102102
"application_id": None,
103-
"created": rfc3339.utcnow(),
104-
"updated": rfc3339.utcnow(),
103+
"created": rfc3339.now_utc(),
104+
"updated": rfc3339.now_utc(),
105105
"title": title,
106106
"description": description,
107107
}
@@ -120,7 +120,7 @@ def set_status(
120120
"""Updates a registered batch job with its status. Additionally, updates its "updated" property."""
121121
kwargs = {
122122
"status": status,
123-
"updated": rfc3339.utcnow(),
123+
"updated": rfc3339.now_utc(),
124124
}
125125

126126
if started:
@@ -608,7 +608,7 @@ def create_job(
608608
job_options: Optional[dict] = None,
609609
) -> JobDict:
610610
assert job_id not in self.db
611-
created = rfc3339.utcnow()
611+
created = rfc3339.now_utc()
612612
self.db[job_id] = {
613613
"job_id": job_id,
614614
"user_id": user_id,
@@ -655,7 +655,7 @@ def set_status(
655655
self._update(
656656
job_id=job_id,
657657
status=status,
658-
updated=rfc3339.datetime(updated) if updated else rfc3339.utcnow(),
658+
updated=rfc3339.datetime(updated) if updated else rfc3339.now_utc(),
659659
)
660660
if started:
661661
self._update(job_id=job_id, started=rfc3339.datetime(started))

openeogeotrellis/utils.py

+3-36
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import pytz
2626
from epsel import on_first_time
2727
from kazoo.client import KazooClient
28+
29+
from openeo.util import rfc3339
2830
from openeo_driver.datacube import DriverVectorCube
2931
from openeo_driver.delayed_vector import DelayedVector
3032
from openeo_driver.util.geometry import GeometryBufferer, reproject_bounding_box
@@ -128,45 +130,10 @@ def normalize_temporal_extent(temporal_extent: Tuple[Union[str, None], Union[str
128130
start, end = temporal_extent
129131
return (
130132
normalize_date(start or "2000-01-01"), # TODO: better fallback start date?
131-
normalize_date(end or utcnow().isoformat())
133+
normalize_date(end or rfc3339.now_utc()),
132134
)
133135

134136

135-
class UtcNowClock:
136-
"""
137-
Helper class to have a mockable wrapper for datetime.datetime.utcnow
138-
(which is not straightforward to mock directly).
139-
"""
140-
141-
# TODO: just start using `time_machine` module for time mocking
142-
143-
_utcnow = _utcnow_orig = datetime.datetime.utcnow
144-
145-
@classmethod
146-
def utcnow(cls) -> datetime.datetime:
147-
return cls._utcnow()
148-
149-
@classmethod
150-
@contextlib.contextmanager
151-
def mock(cls, now: Union[datetime.datetime, str]):
152-
"""Context manager to mock the return value of `utcnow()`."""
153-
if isinstance(now, str):
154-
now = dateutil.parser.parse(now)
155-
cls._utcnow = lambda: now
156-
try:
157-
yield
158-
finally:
159-
cls._utcnow = cls._utcnow_orig
160-
161-
162-
# Alias for general usage
163-
utcnow = UtcNowClock.utcnow
164-
165-
166-
def utcnow_epoch() -> float:
167-
return utcnow().timestamp()
168-
169-
170137
def describe_path(path: Union[Path, str]) -> dict:
171138
path = Path(path)
172139
if path.exists() or path.is_symlink():

tests/test_api_result.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@
6464
from openeogeotrellis.job_registry import ZkJobRegistry
6565
from openeogeotrellis.testing import KazooClientMock, gps_config_overrides, random_name
6666
from openeogeotrellis.utils import (
67-
UtcNowClock,
6867
drop_empty_from_aggregate_polygon_result,
6968
get_jvm,
7069
is_package_available,
@@ -2015,9 +2014,10 @@ def test_extra_validation_unlimited_extent(api100, lc_args):
20152014
(("2020-01-01", None), ("2020-01-05 00:00:00", "2020-02-15 00:00:00")),
20162015
((None, "2019-01-10"), ("2000-01-05 00:00:00", "2019-01-05 00:00:00")),
20172016
])
2018-
def test_load_collection_open_temporal_extent(api100, temporal_extent, expected):
2019-
with UtcNowClock.mock(now="2020-02-20"):
2020-
response = api100.check_result({
2017+
def test_load_collection_open_temporal_extent(api100, temporal_extent, expected, time_machine):
2018+
time_machine.move_to("2020-02-20")
2019+
response = api100.check_result(
2020+
{
20212021
"lc": {
20222022
"process_id": "load_collection",
20232023
"arguments": {

tests/test_backend.py

+22-20
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
from openeogeotrellis.config.s3_config import S3Config
2727
from openeogeotrellis.integrations.kubernetes import k8s_render_manifest_template
2828
from openeogeotrellis.testing import gps_config_overrides
29-
from openeogeotrellis.utils import UtcNowClock, utcnow
3029

3130

3231
def test_extract_application_id():
@@ -674,28 +673,31 @@ def test_k8s_s3_profiles_and_token():
674673
)
675674

676675

677-
def test_k8s_s3_profiles_and_token_must_be_cleanable(backend_config_path, fast_sleep):
678-
# GIVEN a specific time
679-
test_timestamp = utcnow()
680-
test_timestamp_epoch = test_timestamp.timestamp()
681-
682-
# GIVEN the green infinity stone (to control the time dimension)
683-
with UtcNowClock.mock(now=test_timestamp):
684-
# WHEN we render the kubernetes manifest for s3 access
685-
token_path = get_backend_config().batch_job_config_dir / "token"
686-
app_dict = k8s_render_manifest_template(
687-
"batch_job_cfg_secret.yaml.j2",
688-
secret_name="my-app",
689-
job_id="test_id",
690-
token="test",
691-
profile_file_content=S3Config.from_backend_config("j-any", str(token_path))
692-
)
676+
def test_k8s_s3_profiles_and_token_must_be_cleanable(backend_config_path, fast_sleep, time_machine):
677+
time_machine.move_to(1745410732) # Wed 2025-04-23 12:18:52 UTC)
678+
# WHEN we render the kubernetes manifest for s3 access
679+
token_path = get_backend_config().batch_job_config_dir / "token"
680+
app_dict = k8s_render_manifest_template(
681+
"batch_job_cfg_secret.yaml.j2",
682+
secret_name="my-app",
683+
job_id="test_id",
684+
token="test",
685+
profile_file_content=S3Config.from_backend_config("j-any", str(token_path)),
686+
)
693687
# THEN we expect it to be cleanable by having a starttime set to the time it was created.
694688
# We can only clean files if we know they are stale
695689
assert app_dict == dirty_equals.IsPartialDict(
696-
metadata=dirty_equals.IsPartialDict(
697-
annotations=dirty_equals.IsPartialDict(created_at=str(test_timestamp_epoch))
698-
),
690+
{
691+
"apiVersion": "v1",
692+
"kind": "Secret",
693+
"metadata": dirty_equals.IsPartialDict(
694+
{
695+
"labels": {"job_id": "test_id"},
696+
"name": "my-app",
697+
"annotations": dirty_equals.IsPartialDict(created_at="1745403532.0"),
698+
}
699+
),
700+
}
699701
)
700702

701703

tests/test_job_tracker_v2.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ def set_state(self, state: str):
267267

268268
def set_submitted(self):
269269
if not self.start_time:
270-
self.start_time = rfc3339.datetime(dt.datetime.utcnow())
270+
self.start_time = rfc3339.now_utc()
271271
self.state = K8S_SPARK_APP_STATE.SUBMITTED
272272

273273
def set_running(self):
@@ -283,7 +283,7 @@ def set_failed(self):
283283

284284
def set_finish_time(self, force: bool = False):
285285
if not self.finish_time or force:
286-
self.finish_time = rfc3339.datetime(dt.datetime.utcnow())
286+
self.finish_time = rfc3339.now_utc()
287287

288288

289289
class KubernetesMock:
@@ -1625,8 +1625,8 @@ def test_cpu_and_memory_usage_not_in_prometheus(self, caplog):
16251625
k8s_mock.get_namespaced_custom_object.return_value = {
16261626
"status": {
16271627
"applicationState": {"state": K8S_SPARK_APP_STATE.COMPLETED},
1628-
"lastSubmissionAttemptTime": rfc3339.datetime(dt.datetime.utcnow()),
1629-
"terminationTime": rfc3339.datetime(dt.datetime.utcnow()),
1628+
"lastSubmissionAttemptTime": rfc3339.now_utc(),
1629+
"terminationTime": rfc3339.now_utc(),
16301630
}
16311631
}
16321632

tests/test_load_stac.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from openeo_driver.ProcessGraphDeserializer import DEFAULT_TEMPORAL_EXTENT
88
from openeo_driver.backend import BatchJobMetadata, BatchJobs, LoadParameters
99
from openeo_driver.errors import OpenEOApiException
10+
from openeo_driver.util.date_math import now_utc
1011
from openeo_driver.utils import EvalEnv
1112

1213
from openeogeotrellis.load_stac import extract_own_job_info, load_stac
@@ -25,8 +26,11 @@ def test_extract_own_job_info(url, user_id, job_info_id):
2526
batch_jobs = mock.Mock(spec=BatchJobs)
2627

2728
def alices_single_job(job_id, user_id):
28-
return (BatchJobMetadata(id=job_id, status='finished', created=dt.datetime.utcnow())
29-
if job_id == 'j-20240201abc123' and user_id == 'alice' else None)
29+
return (
30+
BatchJobMetadata(id=job_id, status="finished", created=now_utc())
31+
if job_id == "j-20240201abc123" and user_id == "alice"
32+
else None
33+
)
3034

3135
batch_jobs.get_job_info.side_effect = alices_single_job
3236

tests/test_object_storage_workspace.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from pystac import Collection, Extent, SpatialExtent, TemporalExtent, Item, CatalogType, Asset
77
import pytest
88

9+
from openeo_driver.util.date_math import now_utc
910
from openeogeotrellis.workspace import ObjectStorageWorkspace
1011
from openeogeotrellis.workspace.custom_stac_io import CustomStacIO
1112

@@ -237,7 +238,7 @@ def _collection(
237238
asset_href_parts = urlparse(asset_href)
238239
asset_filename = asset_href_parts.path.split("/")[-1]
239240

240-
item = Item(id=asset_filename, geometry=None, bbox=None, datetime=dt.datetime.utcnow(), properties={})
241+
item = Item(id=asset_filename, geometry=None, bbox=None, datetime=now_utc(), properties={})
241242
asset = Asset(href=asset_href)
242243

243244
item.add_asset(key=asset_filename, asset=asset)

0 commit comments

Comments
 (0)