Skip to content

Commit 84a7e3e

Browse files
committed
Issue #389 eliminate deprecated datetime.utcnow() usage
and port some ad-hoc time patching to time_machine
1 parent 45d41ed commit 84a7e3e

File tree

8 files changed

+31
-49
lines changed

8 files changed

+31
-49
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ and start a new "In Progress" section above it.
2424
- Add `namespace` option to `non_standard_process`
2525
- Improve API alignment between `JobRegistryInterface`/`ElasticJobRegistry` and `DoubleJobRegistry` ([Open-EO/openeo-geopyspark-driver#863](https://github.com/Open-EO/openeo-geopyspark-driver/issues/863), [Open-EO/openeo-geopyspark-driver#1123](https://github.com/Open-EO/openeo-geopyspark-driver/issues/1123))
2626
- `export_workspace`: merge `"derived_from"` links of STAC Collections ([Open-EO/openeo-geopyspark-driver#1050](https://github.com/Open-EO/openeo-geopyspark-driver/issues/1050))
27+
- Eliminate usage of deprecated `datetime.utcnow()` ([#389](https://github.com/Open-EO/openeo-python-driver/issues/389))
2728

2829
## 0.132.0
2930

openeo_driver/dummy/dummy_backend.py

+3-9
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import json
33
import numbers
44
import unittest.mock
5-
from datetime import datetime
5+
import datetime
66
from functools import lru_cache
77
from pathlib import Path
88
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, Union
@@ -73,18 +73,12 @@
7373
from openeo_driver.util.http import UrlSafeStructCodec
7474
from openeo_driver.utils import EvalEnv, WhiteListEvalEnv, generate_unique_id
7575

76-
DEFAULT_DATETIME = datetime(2020, 4, 23, 16, 20, 27)
7776

7877
# TODO: eliminate this global state with proper pytest fixture usage!
7978
_collections = {}
8079
_load_collection_calls = {}
8180

8281

83-
def utcnow() -> datetime:
84-
# To simplify testing, we break time.
85-
# TODO: just start using `time_machine` module for time mocking
86-
return DEFAULT_DATETIME
87-
8882

8983
def get_collection(collection_id: str) -> 'DummyDataCube':
9084
return _collections[collection_id]
@@ -145,7 +139,7 @@ class DummySecondaryServices(SecondaryServices):
145139
configuration={"version": "0.5.8"},
146140
attributes={},
147141
title="Test service",
148-
created=datetime(2020, 4, 9, 15, 5, 8)
142+
created=datetime.datetime(2020, 4, 9, 15, 5, 8),
149143
)
150144
]
151145

@@ -710,7 +704,7 @@ def create_job(
710704
id=job_id,
711705
status=JOB_STATUS.CREATED,
712706
process=process,
713-
created=utcnow(),
707+
created=datetime.datetime.now(tz=datetime.timezone.utc),
714708
job_options=job_options,
715709
title=metadata.get("title"),
716710
description=metadata.get("description"),

openeo_driver/util/view_helpers.py

+1-6
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@
44
import flask
55

66

7-
def _utcnow() -> datetime.datetime:
8-
# Allow patching utcnow for unit testing
9-
# TODO: just start using `time_machine` module for time mocking
10-
return datetime.datetime.utcnow()
11-
127

138
def cache_control(
149
max_age=None, no_cache=None, no_store=None,
@@ -32,7 +27,7 @@ def add_cache_control_headers(response: flask.Response) -> flask.Response:
3227
for key, value in settings.items():
3328
setattr(response.cache_control, key, value)
3429
if max_age is not None:
35-
response.expires = _utcnow() + datetime.timedelta(seconds=max_age)
30+
response.expires = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(seconds=max_age)
3631
return response
3732

3833
def decorator(func):

openeo_driver/views.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
send_from_directory,
2727
url_for,
2828
)
29-
from openeo.util import Rfc3339, TimingLogger, deep_get, dict_no_none
29+
from openeo.util import Rfc3339, TimingLogger, deep_get, dict_no_none, rfc3339
3030
from openeo.utils.version import ComparableVersion
3131
from pyproj import CRS
3232
from shapely.geometry import mapping
@@ -1125,9 +1125,7 @@ def job_results_canonical_url() -> str:
11251125
"license": "proprietary", # TODO?
11261126
"extent": {
11271127
"spatial": {"bbox": [[-180, -90, 180, 90]]},
1128-
"temporal": {
1129-
"interval": [[to_datetime(dt.datetime.utcnow()), to_datetime(dt.datetime.utcnow())]]
1130-
},
1128+
"temporal": {"interval": [[rfc3339.utcnow(), rfc3339.utcnow()]]},
11311129
},
11321130
"links": [
11331131
{

tests/test_server.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import re
2-
from datetime import datetime
2+
import datetime
33

44
from openeo_driver.server import build_backend_deploy_metadata
55

66

77
def test_build_backend_deploy_metadata():
88
data = build_backend_deploy_metadata(packages=["openeo", "openeo_driver", "foobarblerghbwop"])
9-
assert data["date"].startswith(datetime.utcnow().strftime("%Y-%m-%dT%H"))
9+
assert data["date"].startswith(datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H"))
1010
assert re.match(r"\d+\.\d+\.\d+", data["versions"]["openeo"])
1111
assert re.match(r"\d+\.\d+\.\d+", data["versions"]["openeo_driver"])
1212
assert data["versions"]["foobarblerghbwop"] == "n/a"

tests/test_view_helpers.py

+16-24
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import datetime
2-
from unittest import mock
32

43
import flask
54
import pytest
@@ -8,6 +7,9 @@
87

98

109
class TestCacheControl:
10+
@pytest.fixture(autouse=True)
11+
def set_time(self, time_machine):
12+
time_machine.move_to("2022-06-01T15:50:00", tick=False)
1113

1214
def test_default(self, ):
1315
app = flask.Flask(__name__)
@@ -22,12 +24,6 @@ def hello():
2224
assert "Cache-Control" not in resp.headers
2325
assert "Expires" not in resp.headers
2426

25-
def _patch_utcnow(self):
26-
return mock.patch(
27-
"openeo_driver.util.view_helpers._utcnow",
28-
return_value=datetime.datetime(2022, 6, 1, 15, 50, 00),
29-
)
30-
3127
@pytest.mark.parametrize("max_age", [
3228
123,
3329
datetime.timedelta(minutes=2, seconds=3),
@@ -40,9 +36,8 @@ def test_max_age(self, max_age):
4036
def hello():
4137
return flask.jsonify(hello="world")
4238

43-
with self._patch_utcnow():
44-
with app.test_client() as client:
45-
resp = client.get("/hello")
39+
with app.test_client() as client:
40+
resp = client.get("/hello")
4641
assert resp.headers["Cache-Control"] == "max-age=123"
4742
assert resp.headers["Expires"] == "Wed, 01 Jun 2022 15:52:03 GMT"
4843

@@ -63,14 +58,13 @@ def hi():
6358
def bye():
6459
return flask.jsonify(bye="world")
6560

66-
with self._patch_utcnow():
67-
with app.test_client() as client:
68-
resp = client.get("/hello")
69-
assert resp.headers["Cache-Control"] == "max-age=123"
70-
resp = client.get("/hi")
71-
assert resp.headers["Cache-Control"] == "max-age=1234"
72-
resp = client.get("/bye")
73-
assert "Cache-Control" not in resp.headers
61+
with app.test_client() as client:
62+
resp = client.get("/hello")
63+
assert resp.headers["Cache-Control"] == "max-age=123"
64+
resp = client.get("/hi")
65+
assert resp.headers["Cache-Control"] == "max-age=1234"
66+
resp = client.get("/bye")
67+
assert "Cache-Control" not in resp.headers
7468

7569
@pytest.mark.parametrize(["status", "caching"], [
7670
(200, True),
@@ -87,9 +81,8 @@ def test_no_cache_on_failure(self, status, caching):
8781
def hello():
8882
return flask.Response(response="hello", status=status)
8983

90-
with self._patch_utcnow():
91-
with app.test_client() as client:
92-
resp = client.get("/hello")
84+
with app.test_client() as client:
85+
resp = client.get("/hello")
9386

9487
if caching:
9588
assert resp.headers["Cache-Control"] == "max-age=123"
@@ -106,8 +99,7 @@ def test_multiple_directives(self):
10699
def hello():
107100
return flask.jsonify(hello="world")
108101

109-
with self._patch_utcnow():
110-
with app.test_client() as client:
111-
resp = client.get("/hello")
102+
with app.test_client() as client:
103+
resp = client.get("/hello")
112104
assert resp.headers["Cache-Control"] == "max-age=123, no-cache, public, must-revalidate"
113105
assert resp.headers["Expires"] == "Wed, 01 Jun 2022 15:52:03 GMT"

tests/test_views.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import re
44
import urllib.parse
55
from contextlib import ExitStack, contextmanager
6-
from datetime import datetime, timedelta
6+
from datetime import datetime, timedelta, timezone
77
from pathlib import Path
88
from typing import Optional
99
from unittest import mock
@@ -1322,6 +1322,7 @@ def _fresh_job_registry(next_job_id="job-1234", output_root: Optional[Path] = No
13221322
)
13231323
yield dummy_backend.DummyBatchJobs._job_registry
13241324

1325+
@time_machine.travel("2024-01-02T13:14:15Z", tick=False)
13251326
def test_create_job_100(self, api100):
13261327
with self._fresh_job_registry(next_job_id="job-245"):
13271328
resp = api100.post('/jobs', headers=self.AUTH_HEADER, json={
@@ -1337,7 +1338,7 @@ def test_create_job_100(self, api100):
13371338
assert job_info.id == "job-245"
13381339
assert job_info.process == {"process_graph": {"foo": {"process_id": "foo", "arguments": {}}}}
13391340
assert job_info.status == "created"
1340-
assert job_info.created == dummy_backend.DEFAULT_DATETIME
1341+
assert job_info.created == datetime(2024, 1, 2, 13, 14, 15, tzinfo=timezone.utc)
13411342
assert job_info.job_options is None
13421343
assert job_info.title == "Foo job"
13431344
assert job_info.description == "Run the `foo` process!"
@@ -1434,6 +1435,7 @@ def test_create_job_100_with_job_options_and_defaults_from_remote_process_defini
14341435
job_info = dummy_backend.DummyBatchJobs._job_registry[TEST_USER, "job-256"]
14351436
assert job_info.job_options == expected_job_options
14361437

1438+
@time_machine.travel("2024-01-02T13:14:15Z")
14371439
def test_create_job_get_metadata(self, api100):
14381440
with self._fresh_job_registry(next_job_id="job-245"):
14391441
resp = api100.post(
@@ -1451,7 +1453,7 @@ def test_create_job_get_metadata(self, api100):
14511453

14521454
resp = api100.get("/jobs/job-245", headers=self.AUTH_HEADER)
14531455
assert resp.assert_status_code(200).json == {
1454-
"created": "2020-04-23T16:20:27Z",
1456+
"created": "2024-01-02T13:14:15Z",
14551457
"id": "job-245",
14561458
"process": {"process_graph": {"foo": {"arguments": {}, "process_id": "foo"}}},
14571459
"progress": 0,

tests/test_workspace.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ def _collection(
200200
extent=Extent(spatial_extent, temporal_extent),
201201
)
202202

203-
item = Item(id=asset_filename, geometry=None, bbox=None, datetime=dt.datetime.utcnow(), properties={})
203+
item = Item(id=asset_filename, geometry=None, bbox=None, datetime=dt.datetime.now(dt.timezone.utc), properties={})
204204

205205
asset_path = root_path / item.id / asset_filename
206206
asset = Asset(href=asset_path.name) # relative to item

0 commit comments

Comments
 (0)