Skip to content

Commit 1c2c532

Browse files
authored
Merge pull request #537 from aknopper/utcnowfix
Change utcnow to now(datetime.timezone.utc)
2 parents d63494f + 8a6651a commit 1c2c532

File tree

13 files changed

+87
-60
lines changed

13 files changed

+87
-60
lines changed

docs/changelog.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
55
This project adheres to `Semantic Versioning <http://semver.org/>`_.
66
Please note that the changes before version 1.10.0 have not been documented.
77

8+
v5.0.2
9+
----------
10+
**Bug Fix Release**
11+
12+
Fixed
13+
^^^^^
14+
- **Build**: Fixed broken 5.0.0 release - Angular frontend is now properly built before publishing to PyPI
15+
- **Deprecation**: Replaced deprecated ``datetime.utcnow()`` with ``datetime.now(timezone.utc)`` throughout codebase
16+
- **Deprecation**: Replaced deprecated ``datetime.utcfromtimestamp()`` with ``datetime.fromtimestamp(..., tz=timezone.utc)``
17+
- **Deprecation**: Replaced ``datetime.UTC`` with ``datetime.timezone.utc`` for Python 3.11+ compatibility
18+
- **Timezone**: Fixed timezone-aware datetime handling in reporting and database operations
19+
- **Tests**: Updated test fixtures to properly handle timezone-aware datetimes
20+
21+
Contributors
22+
^^^^^^^^^^^^
23+
Special thanks to: Alex Knop (@aknopper) for identifying and fixing the deprecated datetime usage
24+
825
v5.0.0
926
----------
1027
**Major Release - Exception Monitoring & Stability Improvements**

flask_monitoringdashboard/controllers/endpoints.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ def get_endpoint_overview(session):
3232
:param session: session for the database
3333
:return: A list of properties for each endpoint that is found in the database
3434
"""
35-
week_ago = datetime.datetime.now(datetime.UTC) - datetime.timedelta(days=7)
36-
now_local = to_local_datetime(datetime.datetime.now(datetime.UTC))
35+
week_ago = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=7)
36+
now_local = to_local_datetime(datetime.datetime.now(datetime.timezone.utc))
3737
today_local = now_local.replace(hour=0, minute=0, second=0, microsecond=0)
3838
today_utc = to_utc_datetime(today_local)
3939

flask_monitoringdashboard/core/cache.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,15 @@ def update_last_requested_cache(endpoint_name):
6969
Use this instead of updating the last requested to the database.
7070
"""
7171
global memory_cache
72-
memory_cache.get(endpoint_name).set_last_requested(datetime.datetime.now(datetime.UTC))
72+
memory_cache.get(endpoint_name).set_last_requested(datetime.datetime.now(datetime.timezone.utc))
7373

7474

7575
def update_duration_cache(endpoint_name, duration):
7676
"""
7777
Use this together with adding a request to the database.
7878
"""
7979
global memory_cache
80-
memory_cache.get(endpoint_name).set_last_requested(datetime.datetime.now(datetime.UTC))
80+
memory_cache.get(endpoint_name).set_last_requested(datetime.datetime.now(datetime.timezone.utc))
8181
memory_cache.get(endpoint_name).set_duration(duration)
8282

8383

flask_monitoringdashboard/core/database_pruning.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import datetime, timedelta
1+
from datetime import datetime, timedelta, timezone
22
from sqlalchemy.orm import Session
33
from flask_monitoringdashboard.core.custom_graph import scheduler
44
from flask_monitoringdashboard.database import (
@@ -23,7 +23,7 @@
2323
def prune_database_older_than_weeks(weeks_to_keep, delete_custom_graph_data):
2424
"""Prune the database of Request and optionally CustomGraph data older than the specified number of weeks"""
2525
with session_scope() as session:
26-
date_to_delete_from = datetime.utcnow() - timedelta(weeks=weeks_to_keep)
26+
date_to_delete_from = datetime.now(timezone.utc) - timedelta(weeks=weeks_to_keep)
2727

2828
# Prune Request table and related Outlier entries
2929
requests_to_delete = (

flask_monitoringdashboard/core/telemetry.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def initialize_telemetry_session(session):
8787
else:
8888
telemetry_user = session.query(TelemetryUser).one()
8989
telemetry_user.times_initialized += 1
90-
telemetry_user.last_initialized = datetime.datetime.now(datetime.UTC)
90+
telemetry_user.last_initialized = datetime.datetime.now(datetime.timezone.utc)
9191
session.commit()
9292

9393
# reset telemetry if declined in previous session

flask_monitoringdashboard/database/__init__.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ class TelemetryUser(Base):
7272
times_initialized = Column(Integer, default=1)
7373
"""For checking the amount of times the app was initialized"""
7474

75-
last_initialized = Column(DateTime, default=datetime.datetime.utcnow)
75+
last_initialized = Column(DateTime, default=datetime.datetime.now(datetime.timezone.utc))
7676
"""Check when was the last time user accessed FMD"""
7777

7878
monitoring_consent = Column(Integer, default=1)
@@ -93,7 +93,7 @@ class Endpoint(Base):
9393
monitor_level = Column(Integer, default=config.monitor_level)
9494
"""0 - disabled, 1 - performance, 2 - outliers, 3 - profiler + outliers"""
9595

96-
time_added = Column(DateTime, default=datetime.datetime.utcnow)
96+
time_added = Column(DateTime, default=datetime.datetime.now(datetime.timezone.utc))
9797
"""Time when the endpoint was added."""
9898

9999
version_added = Column(String(100), default=config.version)
@@ -118,7 +118,7 @@ class Request(Base):
118118
duration = Column(Float, nullable=False)
119119
"""Processing time of the request in milliseconds."""
120120

121-
time_requested = Column(DateTime, default=datetime.datetime.utcnow)
121+
time_requested = Column(DateTime, default=datetime.datetime.now(datetime.timezone.utc))
122122
"""Moment when the request was handled."""
123123

124124
version_requested = Column(String(100), default=config.version)
@@ -225,7 +225,7 @@ class CustomGraph(Base):
225225
title = Column(String(250), nullable=False, unique=True)
226226
"""Title of this graph."""
227227

228-
time_added = Column(DateTime, default=datetime.datetime.utcnow)
228+
time_added = Column(DateTime, default=datetime.datetime.now(datetime.timezone.utc))
229229
"""When the graph was first added to the dashboard."""
230230

231231
version_added = Column(String(100), default=config.version)
@@ -244,7 +244,7 @@ class CustomGraphData(Base):
244244
graph = relationship(CustomGraph, backref="data")
245245
"""Graph for which the data is collected."""
246246

247-
time = Column(DateTime, default=datetime.datetime.utcnow)
247+
time = Column(DateTime, default=datetime.datetime.now(datetime.timezone.utc))
248248
"""Moment when the data is collected."""
249249

250250
value = Column(Float)

flask_monitoringdashboard/database/endpoint.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ def update_last_requested(session, endpoint_name, timestamp=None):
150150
:param endpoint_name: name of the endpoint
151151
:param timestamp: optional timestamp. If not given, timestamp is current time
152152
"""
153-
ts = timestamp if timestamp else datetime.datetime.now(datetime.UTC)
153+
ts = timestamp if timestamp else datetime.datetime.now(datetime.timezone.utc)
154154
session.query(Endpoint).filter(Endpoint.name == endpoint_name).update(
155155
{Endpoint.last_requested: ts}
156156
)

flask_monitoringdashboard/views/reporting.py

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import datetime
1+
from datetime import datetime, timezone
22

33
from flask import request, jsonify
44
from sqlalchemy import and_
@@ -8,18 +8,21 @@
88
from flask_monitoringdashboard.core.auth import secure
99
from flask_monitoringdashboard.core.date_interval import DateInterval
1010
from flask_monitoringdashboard.core.telemetry import post_to_back_if_telemetry_enabled
11-
from flask_monitoringdashboard.core.reporting.questions.median_latency import \
12-
MedianLatency
11+
from flask_monitoringdashboard.core.reporting.questions.median_latency import (
12+
MedianLatency,
13+
)
1314
from flask_monitoringdashboard.core.reporting.questions.status_code_distribution import (
1415
StatusCodeDistribution,
1516
)
1617
from flask_monitoringdashboard.database import session_scope
1718
from flask_monitoringdashboard.database.endpoint import get_endpoints
18-
from flask_monitoringdashboard.database.request import create_time_based_sample_criterion
19+
from flask_monitoringdashboard.database.request import (
20+
create_time_based_sample_criterion,
21+
)
1922

2023

2124
def get_date(p):
22-
return datetime.utcfromtimestamp(int(request.args.get(p)))
25+
return datetime.fromtimestamp(int(request.args.get(p)), tz=timezone.utc)
2326

2427

2528
def make_endpoint_summary(endpoint, requests_criterion, baseline_requests_criterion):
@@ -33,13 +36,14 @@ def make_endpoint_summary(endpoint, requests_criterion, baseline_requests_criter
3336
)
3437

3538
for question in questions:
36-
answer = question.get_answer(endpoint, requests_criterion,
37-
baseline_requests_criterion)
39+
answer = question.get_answer(
40+
endpoint, requests_criterion, baseline_requests_criterion
41+
)
3842

3943
if answer.is_significant():
40-
summary['has_anything_significant'] = True
44+
summary["has_anything_significant"] = True
4145

42-
summary['answers'].append(answer.serialize())
46+
summary["answers"].append(answer.serialize())
4347

4448
return summary
4549

@@ -49,52 +53,54 @@ def make_endpoint_summaries(requests_criterion, baseline_requests_criterion):
4953

5054
with session_scope() as db_session:
5155
for endpoint in get_endpoints(db_session):
52-
endpoint_summary = make_endpoint_summary(endpoint, requests_criterion,
53-
baseline_requests_criterion)
56+
endpoint_summary = make_endpoint_summary(
57+
endpoint, requests_criterion, baseline_requests_criterion
58+
)
5459
endpoint_summaries.append(endpoint_summary)
5560

5661
return dict(summaries=endpoint_summaries)
5762

5863

59-
@blueprint.route('/api/reporting/make_report/intervals', methods=['POST'])
64+
@blueprint.route("/api/reporting/make_report/intervals", methods=["POST"])
6065
@secure
6166
def make_report_intervals():
62-
post_to_back_if_telemetry_enabled(**{'name': 'reporting/make_reports/intervals'})
67+
post_to_back_if_telemetry_enabled(**{"name": "reporting/make_reports/intervals"})
6368
arguments = request.json
6469

6570
try:
6671
interval = DateInterval(
67-
datetime.fromtimestamp(int(arguments['interval']['from'])),
68-
datetime.fromtimestamp(int(arguments['interval']['to'])),
72+
datetime.fromtimestamp(int(arguments["interval"]["from"]), tz=timezone.utc),
73+
datetime.fromtimestamp(int(arguments["interval"]["to"]), tz=timezone.utc),
6974
)
7075

7176
baseline_interval = DateInterval(
72-
datetime.fromtimestamp(int(arguments['baseline_interval']['from'])),
73-
datetime.fromtimestamp(int(arguments['baseline_interval']['to'])),
77+
datetime.fromtimestamp(int(arguments["baseline_interval"]["from"]), tz=timezone.utc),
78+
datetime.fromtimestamp(int(arguments["baseline_interval"]["to"]), tz=timezone.utc),
7479
)
7580

7681
except Exception:
77-
return 'Invalid payload', 422
82+
return "Invalid payload", 422
7883

7984
baseline_requests_criterion = create_time_based_sample_criterion(
80-
baseline_interval.start_date(),
81-
baseline_interval.end_date())
82-
requests_criterion = create_time_based_sample_criterion(interval.start_date(),
83-
interval.end_date())
85+
baseline_interval.start_date(), baseline_interval.end_date()
86+
)
87+
requests_criterion = create_time_based_sample_criterion(
88+
interval.start_date(), interval.end_date()
89+
)
8490

8591
summaries = make_endpoint_summaries(requests_criterion, baseline_requests_criterion)
8692

8793
return jsonify(summaries)
8894

8995

90-
@blueprint.route('/api/reporting/make_report/commits', methods=['POST'])
96+
@blueprint.route("/api/reporting/make_report/commits", methods=["POST"])
9197
@secure
9298
def make_report_commits():
93-
post_to_back_if_telemetry_enabled(**{'name': 'reporting/make_reports/commits'})
99+
post_to_back_if_telemetry_enabled(**{"name": "reporting/make_reports/commits"})
94100
arguments = request.json
95101

96-
baseline_commit_version = arguments['baseline_commit_version']
97-
commit_version = arguments['commit_version']
102+
baseline_commit_version = arguments["baseline_commit_version"]
103+
commit_version = arguments["commit_version"]
98104

99105
if None in [baseline_commit_version, commit_version]:
100106
return dict(message="Please select two commits"), 422

tests/api/test_custom.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import datetime, timedelta
1+
from datetime import datetime, timedelta, timezone
22

33
from flask_monitoringdashboard.database import CustomGraph
44

@@ -17,7 +17,7 @@ def test_custom_graphs(dashboard_user, custom_graph, session):
1717

1818

1919
def test_custom_graph_data(dashboard_user, custom_graph, custom_graph_data):
20-
today = datetime.utcnow()
20+
today = datetime.now(timezone.utc)
2121
yesterday = today - timedelta(days=1)
2222
response = dashboard_user.get('dashboard/api/custom_graph/{id}/{start}/{end}'.format(
2323
id=custom_graph.graph_id,

tests/api/test_reporting.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import sys
2-
from datetime import datetime, timedelta
2+
from datetime import datetime, timedelta, timezone
33
import pytest
44

55
from flask_monitoringdashboard.database import Endpoint
@@ -11,24 +11,24 @@ def test_make_report_get(dashboard_user):
1111
assert not response.is_json
1212

1313

14-
@pytest.mark.parametrize('request_1__time_requested', [datetime.utcnow() - timedelta(hours=6)])
14+
@pytest.mark.parametrize('request_1__time_requested', [datetime.now(timezone.utc) - timedelta(hours=6)])
1515
@pytest.mark.parametrize('request_1__duration', [5000])
1616
@pytest.mark.parametrize('request_1__status_code', [500])
17-
@pytest.mark.parametrize('request_2__time_requested', [datetime.utcnow() - timedelta(days=1, hours=6)])
17+
@pytest.mark.parametrize('request_2__time_requested', [datetime.now(timezone.utc) - timedelta(days=1, hours=6)])
1818
@pytest.mark.parametrize('request_2__duration', [100])
1919
@pytest.mark.skipif(sys.version_info < (3, ), reason="For some reason, this doesn't work in python 2.7.")
2020
def test_make_report_post_not_significant(dashboard_user, endpoint, request_1, request_2, session):
21-
epoch = datetime(1970, 1, 1)
21+
epoch = datetime(1970, 1, 1, tzinfo=timezone.utc)
2222
response = dashboard_user.post(
2323
'dashboard/api/reporting/make_report/intervals',
2424
json={
2525
'interval': {
26-
'from': (datetime.utcnow() - timedelta(days=1) - epoch).total_seconds(),
27-
'to': (datetime.utcnow() - epoch).total_seconds(),
26+
'from': (datetime.now(timezone.utc) - timedelta(days=1) - epoch).total_seconds(),
27+
'to': (datetime.now(timezone.utc) - epoch).total_seconds(),
2828
},
2929
'baseline_interval': {
30-
'from': (datetime.utcnow() - timedelta(days=2) - epoch).total_seconds(),
31-
'to': (datetime.utcnow() - timedelta(days=1) - epoch).total_seconds(),
30+
'from': (datetime.now(timezone.utc) - timedelta(days=2) - epoch).total_seconds(),
31+
'to': (datetime.now(timezone.utc) - timedelta(days=1) - epoch).total_seconds(),
3232
},
3333
},
3434
)

0 commit comments

Comments
 (0)