From 53715ea1268b7ea4aa459e87f6ae8c203c4e7ea6 Mon Sep 17 00:00:00 2001 From: sbgap Date: Fri, 6 Feb 2026 13:08:44 +0100 Subject: [PATCH 1/3] chore: update datetime.utcnow to dateime.now(UTC) --- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 8 +++--- .github/workflows/tests.yml | 2 +- alerta/auth/oidc.py | 4 +-- alerta/auth/utils.py | 4 +-- alerta/commands.py | 4 +-- alerta/database/backends/mongodb/base.py | 26 ++++++++++---------- alerta/database/backends/mongodb/utils.py | 12 ++++----- alerta/database/backends/postgres/base.py | 10 ++++---- alerta/dev.py | 4 +-- alerta/management/views.py | 2 +- alerta/models/alert.py | 30 +++++++++++------------ alerta/models/blackout.py | 12 ++++----- alerta/models/escalation_rule.py | 4 +-- alerta/models/heartbeat.py | 12 ++++----- alerta/models/history.py | 4 +-- alerta/models/key.py | 10 ++++---- alerta/models/note.py | 4 +-- alerta/models/notification_delay.py | 4 +-- alerta/models/notification_history.py | 4 +-- alerta/models/notification_rule.py | 6 ++--- alerta/models/on_call.py | 4 +-- alerta/models/user.py | 6 ++--- alerta/plugins/notification_rule.py | 4 +-- alerta/utils/audit.py | 4 +-- alerta/utils/client.py | 2 +- alerta/utils/format.py | 2 +- alerta/views/alerts.py | 4 +-- tests/test_blackouts.py | 4 +-- 29 files changed, 99 insertions(+), 99 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 43d477833..805667ce9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: - python-version: ['3.10', '3.11'] + python-version: ['3.12'] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c0ea22d47..7e90053a9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,10 +29,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Python 3.11 + - name: Set up Python 3.12 uses: actions/setup-python@v5 with: - python-version: 3.11 + python-version: 3.12 - name: Install dependencies id: install-deps run: | @@ -64,10 +64,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Python 3.11 + - name: Set up Python 3.12 uses: actions/setup-python@v5 with: - python-version: 3.11 + python-version: 3.12 - name: Build id: build run: | diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 48674d601..1906e588f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,7 +29,7 @@ jobs: strategy: matrix: - python-version: ['3.10', '3.12'] + python-version: ['3.12'] steps: - uses: actions/checkout@v4 diff --git a/alerta/auth/oidc.py b/alerta/auth/oidc.py index 7c6212d20..d10cc68e1 100644 --- a/alerta/auth/oidc.py +++ b/alerta/auth/oidc.py @@ -1,5 +1,5 @@ import json -from datetime import datetime, timedelta +from datetime import UTC, datetime, timedelta from uuid import uuid4 import jwt @@ -96,7 +96,7 @@ def openid(): data['client_secret'] = current_app.config['OAUTH2_CLIENT_SECRET'] r = requests.post(token_endpoint, data) elif preferred_token_auth_method == 'client_secret_jwt': - now = datetime.utcnow() + now = datetime.now(UTC) payload = dict( iss=request.json['clientId'], sub=request.json['clientId'], diff --git a/alerta/auth/utils.py b/alerta/auth/utils.py index 787274c7f..abcb5f4a3 100644 --- a/alerta/auth/utils.py +++ b/alerta/auth/utils.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import UTC, datetime, timedelta from typing import TYPE_CHECKING, Any, List, cast from urllib.parse import urljoin from uuid import uuid4 @@ -49,7 +49,7 @@ def get_customers(login: str, groups: List[str]) -> List[str]: def create_token(user_id: str, name: str, login: str, provider: str, customers: List[str], scopes: List[str], email: str = None, email_verified: bool = None, picture: str = None, **kwargs) -> 'Jwt': - now = datetime.utcnow() + now = datetime.now(UTC) return Jwt( iss=request.url_root, typ='Bearer', diff --git a/alerta/commands.py b/alerta/commands.py index f9f2b4739..40a085927 100644 --- a/alerta/commands.py +++ b/alerta/commands.py @@ -1,5 +1,5 @@ import sys -from datetime import datetime, timedelta +from datetime import UTC, datetime, timedelta from typing import Any, Dict import click @@ -63,7 +63,7 @@ def key(username, want_key, scopes, duration, text, customer, all, force): raise click.UsageError('Can only set API key with "--username".') scopes = [Scope(s) for s in scopes] or [Scope.admin, Scope.write, Scope.read] - expires = datetime.utcnow() + timedelta(seconds=duration) if duration else None + expires = datetime.now(UTC) + timedelta(seconds=duration) if duration else None text = text or 'Created by alertad script' def create_key(admin, key=None): diff --git a/alerta/database/backends/mongodb/base.py b/alerta/database/backends/mongodb/base.py index f5730b125..8b1e3a2c6 100644 --- a/alerta/database/backends/mongodb/base.py +++ b/alerta/database/backends/mongodb/base.py @@ -1,5 +1,5 @@ from collections import defaultdict -from datetime import date, datetime, time, timedelta +from datetime import UTC, date, datetime, time, timedelta from flask import current_app from pymongo import ASCENDING, TEXT, MongoClient, ReturnDocument @@ -187,7 +187,7 @@ def is_flapping(self, alert, window=1800, count=2): }}, {'$unwind': '$history'}, {'$match': { - 'history.updateTime': {'$gt': datetime.utcnow() - timedelta(seconds=window)}, + 'history.updateTime': {'$gt': datetime.now(UTC) - timedelta(seconds=window)}, 'history.type': 'severity' }}, {'$group': {'_id': '$history.type', 'count': {'$sum': 1}}} @@ -211,7 +211,7 @@ def dedup_alert(self, alert, history): 'customer': alert.customer } - now = datetime.utcnow() + now = datetime.now(UTC) update = { '$set': { 'status': alert.status, @@ -1366,7 +1366,7 @@ def get_heartbeats_by_status(self, status=None, query=None, page=None, page_size if status: pipeline.extend([ {'$addFields': {'timeoutInMs': {'$multiply': ['$timeout', 1000]}}}, - {'$addFields': {'isExpired': {'$gt': [{'$subtract': [datetime.utcnow(), '$receiveTime']}, '$timeoutInMs']}}}, + {'$addFields': {'isExpired': {'$gt': [{'$subtract': [datetime.now(UTC), '$receiveTime']}, '$timeoutInMs']}}}, {'$addFields': {'isSlow': {'$gt': [{'$subtract': ['$receiveTime', '$createTime']}, max_latency]}}} ]) match_or = list() @@ -1444,7 +1444,7 @@ def update_key_last_used(self, key): return self.get_db().keys.update_one( {'$or': [{'key': key}, {'_id': key}]}, { - '$set': {'lastUsedTime': datetime.utcnow()}, + '$set': {'lastUsedTime': datetime.now(UTC)}, '$inc': {'count': 1} } ).matched_count == 1 @@ -1511,7 +1511,7 @@ def get_user_by_hash(self, hash): def update_last_login(self, id): return self.get_db().users.update_one( {'_id': id}, - update={'$set': {'lastLogin': datetime.utcnow()}} + update={'$set': {'lastLogin': datetime.now(UTC)}} ).matched_count == 1 def update_user(self, id, **kwargs): @@ -1553,7 +1553,7 @@ def delete_user(self, id): def set_email_hash(self, id, hash): return self.get_db().users.update_one( {'_id': id}, - update={'$set': {'hash': hash, 'updateTime': datetime.utcnow()}} + update={'$set': {'hash': hash, 'updateTime': datetime.now(UTC)}} ).matched_count == 1 # GROUPS @@ -1769,7 +1769,7 @@ def get_customer_notes(self, customer, page=None, page_size=None): return self.get_db().notes.find({'customer': customer}).skip((page - 1) * page_size).limit(page_size) def update_note(self, id, **kwargs): - kwargs['updateTime'] = datetime.utcnow() + kwargs['updateTime'] = datetime.now(UTC) return self.get_db().notes.find_one_and_update( {'_id': id}, update={'$set': kwargs}, @@ -1855,12 +1855,12 @@ def get_expired(self, expired_threshold, info_threshold): # and 'informational' alerts older than "info_threshold" seconds if expired_threshold: - expired_seconds_ago = datetime.utcnow() - timedelta(seconds=expired_threshold) + expired_seconds_ago = datetime.now(UTC) - timedelta(seconds=expired_threshold) self.get_db().alerts.delete_many( {'status': {'$in': ['closed', 'expired']}, 'lastReceiveTime': {'$lt': expired_seconds_ago}}) if info_threshold: - info_seconds_ago = datetime.utcnow() - timedelta(seconds=info_threshold) + info_seconds_ago = datetime.now(UTC) - timedelta(seconds=info_threshold) self.get_db().alerts.delete_many({'severity': alarm_model.DEFAULT_INFORM_SEVERITY, 'lastReceiveTime': {'$lt': info_seconds_ago}}) # get list of alerts to be newly expired @@ -1870,7 +1870,7 @@ def get_expired(self, expired_threshold, info_threshold): 'computedTimeout': {'$multiply': [{'$ifNull': ['$timeout', current_app.config['ALERT_TIMEOUT']]}, 1000]} }}, {'$addFields': { - 'isExpired': {'$lt': [{'$add': ['$lastReceiveTime', '$computedTimeout']}, datetime.utcnow()]} + 'isExpired': {'$lt': [{'$add': ['$lastReceiveTime', '$computedTimeout']}, datetime.now(UTC)]} }}, {'$match': {'isExpired': True, 'computedTimeout': {'$ne': 0}}} ] @@ -1920,7 +1920,7 @@ def get_unshelve(self): 'computedTimeout': {'$multiply': [{'$ifNull': ['$history.timeout', current_app.config['SHELVE_TIMEOUT']]}, 1000]} }}, {'$addFields': { - 'isExpired': {'$lt': [{'$add': ['$updateTime', '$computedTimeout']}, datetime.utcnow()]} + 'isExpired': {'$lt': [{'$add': ['$updateTime', '$computedTimeout']}, datetime.now(UTC)]} }}, {'$match': {'isExpired': True, 'computedTimeout': {'$ne': 0}}} ] @@ -1970,7 +1970,7 @@ def get_unack(self): 'computedTimeout': {'$multiply': [{'$ifNull': ['$history.timeout', current_app.config['ACK_TIMEOUT']]}, 1000]} }}, {'$addFields': { - 'isExpired': {'$lt': [{'$add': ['$updateTime', '$computedTimeout']}, datetime.utcnow()]} + 'isExpired': {'$lt': [{'$add': ['$updateTime', '$computedTimeout']}, datetime.now(UTC)]} }}, {'$match': {'isExpired': True, 'computedTimeout': {'$ne': 0}}} ] diff --git a/alerta/database/backends/mongodb/utils.py b/alerta/database/backends/mongodb/utils.py index ae0bca115..d073075d9 100644 --- a/alerta/database/backends/mongodb/utils.py +++ b/alerta/database/backends/mongodb/utils.py @@ -1,7 +1,7 @@ import json import re from collections import namedtuple -from datetime import datetime +from datetime import UTC, datetime from typing import Any, Dict, List # noqa import pytz @@ -247,11 +247,11 @@ def from_params(params: ImmutableMultiDict, customers=None, query_time=None): if status: query['$or'] = list() if BlackoutStatus.Active in status: - query['$or'].append({'$and': [{'startTime': {'$lte': datetime.utcnow()}}, {'endTime': {'$gt': datetime.utcnow()}}]}) + query['$or'].append({'$and': [{'startTime': {'$lte': datetime.now(UTC)}}, {'endTime': {'$gt': datetime.now(UTC)}}]}) if BlackoutStatus.Pending in status: - query['$or'].append({'startTime': {'$gt': datetime.utcnow()}}) + query['$or'].append({'startTime': {'$gt': datetime.now(UTC)}}) if BlackoutStatus.Expired in status: - query['$or'].append({'endTime': {'$lte': datetime.utcnow()}}) + query['$or'].append({'endTime': {'$lte': datetime.now(UTC)}}) # filter, sort-by, group-by query = QueryBuilder.filter_query(params, Blackouts.VALID_PARAMS, query) @@ -527,9 +527,9 @@ def from_params(params: MultiDict, customers=None, query_time=None): if status: query['$or'] = list() if ApiKeyStatus.Active in status: - query['$or'].append({'expireTime': {'$gte': datetime.utcnow()}}) + query['$or'].append({'expireTime': {'$gte': datetime.now(UTC)}}) if ApiKeyStatus.Expired in status: - query['$or'].append({'expireTime': {'$lt': datetime.utcnow()}}) + query['$or'].append({'expireTime': {'$lt': datetime.now(UTC)}}) # filter, sort-by, group-by query = QueryBuilder.filter_query(params, ApiKeys.VALID_PARAMS, query) diff --git a/alerta/database/backends/postgres/base.py b/alerta/database/backends/postgres/base.py index df3172bbf..69fbfab6f 100644 --- a/alerta/database/backends/postgres/base.py +++ b/alerta/database/backends/postgres/base.py @@ -1,7 +1,7 @@ import threading import time from collections import defaultdict, namedtuple -from datetime import datetime, timedelta +from datetime import UTC, datetime, timedelta from uuid import uuid4 import psycopg2 @@ -636,7 +636,7 @@ def get_escalate(self): FROM alerts WHERE status='open' AND last_receive_time < %(etime)s """ - return self._fetchall(select, {'etime': datetime.utcnow() - timedelta(minutes=current_app.config['ESCALATE_TIME'])}, limit=1000) + return self._fetchall(select, {'etime': datetime.now(UTC) - timedelta(minutes=current_app.config['ESCALATE_TIME'])}, limit=1000) def get_alert_history(self, alert, page=None, page_size=None): select = """ @@ -1505,7 +1505,7 @@ def create_notification_rule_history(self, update_type: str, notification_rule): data['reactivate'] = data['reactivate'].isoformat() if data.get('reactivate') is not None else None data['createTime'] = data['createTime'].isoformat() if data.get('createTime') is not None else None data['delayTime'] = str(data['delayTime']) if data.get('delayTime') is not None else None - return self._insert(insert, {'data': data, 'id': notification_rule.id, 'user': notification_rule.user, 'type': update_type, 'create_time': datetime.utcnow()}) + return self._insert(insert, {'data': data, 'id': notification_rule.id, 'user': notification_rule.user, 'type': update_type, 'create_time': datetime.now(UTC)}) def get_notification_rule_history(self, rule_id: str, page, page_size): select = """ @@ -1773,7 +1773,7 @@ def confirm_notification_history(self, id): WHERE id=%(id)s RETURNING * """ - return self._updateone(update, {'id': id, 'time': datetime.utcnow()}, returning=True) + return self._updateone(update, {'id': id, 'time': datetime.now(UTC)}, returning=True) # ESCALATION RULES @@ -1853,7 +1853,7 @@ def get_escalation_alerts(self): SELECT * from alert_tags WHERE alert_tags.id NOT IN (SELECT DISTINCT alert_excluded.id FROM alert_excluded) """ - return self._fetchall(select, {'now': datetime.utcnow()}, limit='ALL') + return self._fetchall(select, {'now': datetime.now(UTC)}, limit='ALL') def update_escalation_rule(self, id, **kwargs): update = """ diff --git a/alerta/dev.py b/alerta/dev.py index 1e1a776ec..2922afc93 100644 --- a/alerta/dev.py +++ b/alerta/dev.py @@ -1,5 +1,5 @@ -from datetime import datetime +from datetime import UTC, datetime BUILD_NUMBER = 'DEV' -BUILD_DATE = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ') +BUILD_DATE = datetime.now(UTC).strftime('%Y-%m-%dT%H:%M:%SZ') BUILD_VCS_NUMBER = 'HEAD' diff --git a/alerta/management/views.py b/alerta/management/views.py index cb73fb86c..6ca6d8d3f 100644 --- a/alerta/management/views.py +++ b/alerta/management/views.py @@ -142,7 +142,7 @@ def health_check(): try: heartbeats = Heartbeat.find_all() for heartbeat in heartbeats: - delta = datetime.datetime.utcnow() - heartbeat.receive_time + delta = datetime.datetime.now(datetime.UTC) - heartbeat.receive_time threshold = int(heartbeat.timeout) * 4 if delta.seconds > threshold: return f'HEARTBEAT_STALE: {heartbeat.origin}', 503 diff --git a/alerta/models/alert.py b/alerta/models/alert.py index d7e60b41b..8f62154f3 100644 --- a/alerta/models/alert.py +++ b/alerta/models/alert.py @@ -1,7 +1,7 @@ import os import platform import sys -from datetime import datetime +from datetime import UTC, datetime from typing import Optional # noqa from typing import Any, Dict, List, Tuple, Union from uuid import uuid4 @@ -68,7 +68,7 @@ def __init__(self, resource: str, event: str, **kwargs) -> None: self.attributes = kwargs.get('attributes', None) or dict() self.origin = kwargs.get('origin', None) or f'{os.path.basename(sys.argv[0])}/{platform.uname()[1]}' self.event_type = kwargs.get('event_type', kwargs.get('type', None)) or 'exceptionAlert' - self.create_time = kwargs.get('create_time', None) or datetime.utcnow() + self.create_time = kwargs.get('create_time', None) or datetime.now(UTC) self.day = self.create_time.strftime('%a') self.time = self.create_time.time() self.timeout = timeout @@ -79,7 +79,7 @@ def __init__(self, resource: str, event: str, **kwargs) -> None: self.repeat = kwargs.get('repeat', None) self.previous_severity = kwargs.get('previous_severity', None) self.trend_indication = kwargs.get('trend_indication', None) - self.receive_time = kwargs.get('receive_time', None) or datetime.utcnow() + self.receive_time = kwargs.get('receive_time', None) or datetime.now(UTC) self.last_receive_id = kwargs.get('last_receive_id', None) self.last_receive_time = kwargs.get('last_receive_time', None) self.update_time = kwargs.get('update_time', None) @@ -321,7 +321,7 @@ def _get_hist_info(self, action=None): # de-duplicate an alert def deduplicate(self, duplicate_of) -> 'Alert': - now = datetime.utcnow() + now = datetime.now(UTC) status, previous_value, previous_status, _ = self._get_hist_info() @@ -374,7 +374,7 @@ def deduplicate(self, duplicate_of) -> 'Alert': # correlate an alert def update(self, correlate_with) -> 'Alert': - now = datetime.utcnow() + now = datetime.now(UTC) self.previous_severity = db.get_severity(self) self.trend_indication = alarm_model.trend(self.previous_severity, self.severity) @@ -418,7 +418,7 @@ def update(self, correlate_with) -> 'Alert': # create an alert def create(self) -> 'Alert': - now = datetime.utcnow() + now = datetime.now(UTC) trend_indication = alarm_model.trend(alarm_model.DEFAULT_PREVIOUS_SEVERITY, self.severity) @@ -469,7 +469,7 @@ def is_suppressed(self) -> bool: # set alert status def set_status(self, status: str, text: str = '', timeout: int = None) -> 'Alert': - now = datetime.utcnow() + now = datetime.now(UTC) timeout = timeout or current_app.config['ALERT_TIMEOUT'] history = History( @@ -621,7 +621,7 @@ def get_groups(query: Query = None) -> List[str]: def create_multiple_alerts(alerts: 'list[Alert]'): if len(alerts) < 1: return [] - now = datetime.utcnow() + now = datetime.now(UTC) for alert in alerts: trend_indication = alarm_model.trend(alarm_model.DEFAULT_PREVIOUS_SEVERITY, alert.severity) @@ -657,7 +657,7 @@ def create_multiple_alerts(alerts: 'list[Alert]'): def dedup_multiple_alerts(duplicates: 'list[dict[str, Alert]]'): if len(duplicates) < 1: return [] - now = datetime.utcnow() + now = datetime.now(UTC) alerts = [] for alert in duplicates: alert, duplicate_of = (alert['alert'], alert['duplicate']) @@ -716,7 +716,7 @@ def dedup_multiple_alerts(duplicates: 'list[dict[str, Alert]]'): def update_multiple(correlates: 'list[dict[str, Alert]]') -> 'Alert': if len(correlates) < 1: return [] - now = datetime.utcnow() + now = datetime.now(UTC) alerts = [] for alert in correlates: alert, correlate_with = (alert['alert'], alert['correlate']) @@ -777,7 +777,7 @@ def add_note(self, text: str) -> Note: value=self.value, text=text, change_type=ChangeType.note, - update_time=datetime.utcnow(), + update_time=datetime.now(UTC), user=g.login ) db.add_history(self.id, history) @@ -797,7 +797,7 @@ def delete_note(self, note_id): value=self.value, text='note dismissed', change_type=ChangeType.dismiss, - update_time=datetime.utcnow(), + update_time=datetime.now(UTC), user=g.login ) db.add_history(self.id, history) @@ -812,7 +812,7 @@ def housekeeping(expired_threshold: int, info_threshold: int) -> Tuple[List['Ale ) def from_status(self, status: str, text: str = '', timeout: int = None) -> 'Alert': - now = datetime.utcnow() + now = datetime.now(UTC) self.timeout = timeout or current_app.config['ALERT_TIMEOUT'] history = [History( @@ -841,7 +841,7 @@ def from_status(self, status: str, text: str = '', timeout: int = None) -> 'Aler @staticmethod def from_action_multiple(alerts, action, text='', timeout=None): - now = datetime.utcnow() + now = datetime.now(UTC) statuses = Alert._get_hist_infos(alerts, action) updates = [] for a in alerts: @@ -897,7 +897,7 @@ def from_action_multiple(alerts, action, text='', timeout=None): return [Alert.from_db(alert) for alert in db.set_alerts(updates)] def from_action(self, action: str, text: str = '', timeout: int = None) -> 'Alert': - now = datetime.utcnow() + now = datetime.now(UTC) status, _, previous_status, previous_timeout = self._get_hist_info(action) diff --git a/alerta/models/blackout.py b/alerta/models/blackout.py index a4d02bde7..863e7add6 100644 --- a/alerta/models/blackout.py +++ b/alerta/models/blackout.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import UTC, datetime, timedelta from typing import Any, Dict, List, Optional, Tuple, Union from uuid import uuid4 @@ -34,7 +34,7 @@ def __init__(self, environment: str, **kwargs) -> None: if kwargs.get('origin') == '': raise ValueError('origin can not be an empty string') - start_time = kwargs.get('start_time', None) or datetime.utcnow() + start_time = kwargs.get('start_time', None) or datetime.now(UTC) if kwargs.get('end_time', None): end_time = kwargs['end_time'] duration = int((end_time - start_time).total_seconds()) @@ -57,7 +57,7 @@ def __init__(self, environment: str, **kwargs) -> None: self.remaining = kwargs.get('remaining', duration) self.user = kwargs.get('user', None) - self.create_time = kwargs['create_time'] if 'create_time' in kwargs else datetime.utcnow() + self.create_time = kwargs['create_time'] if 'create_time' in kwargs else datetime.now(UTC) self.text = kwargs.get('text', None) if self.environment: @@ -81,7 +81,7 @@ def __init__(self, environment: str, **kwargs) -> None: @property def status(self): - now = datetime.utcnow() + now = datetime.now(UTC) if self.start_time <= now < self.end_time: return BlackoutStatus.Active if self.start_time > now: @@ -200,8 +200,8 @@ def from_record(cls, rec) -> 'Blackout': tags=rec.tags, origin=rec.origin if rec.origin != '' else None, customer=rec.customer, - start_time=rec.start_time, - end_time=rec.end_time, + start_time=rec.start_time.replace(tzinfo=UTC) if rec.start_time is not None else None, + end_time=rec.end_time.replace(tzinfo=UTC) if rec.end_time is not None else None, duration=rec.duration, remaining=rec.remaining, user=rec.user, diff --git a/alerta/models/escalation_rule.py b/alerta/models/escalation_rule.py index a25bca895..12ca3dfa0 100644 --- a/alerta/models/escalation_rule.py +++ b/alerta/models/escalation_rule.py @@ -1,4 +1,4 @@ -from datetime import datetime, time, timedelta +from datetime import UTC, datetime, time, timedelta from typing import Any, Dict, List, Optional, Tuple, Union from uuid import uuid4 @@ -50,7 +50,7 @@ def __init__( self.user = kwargs.get('user', None) self.create_time = ( - kwargs['create_time'] if 'create_time' in kwargs else datetime.utcnow() + kwargs['create_time'] if 'create_time' in kwargs else datetime.now(UTC) ) if self.environment: diff --git a/alerta/models/heartbeat.py b/alerta/models/heartbeat.py index 56dac0f3f..88e6cf4ab 100644 --- a/alerta/models/heartbeat.py +++ b/alerta/models/heartbeat.py @@ -1,7 +1,7 @@ import os import platform import sys -from datetime import datetime +from datetime import UTC, datetime from typing import Any, Dict, List, Optional, Tuple, Union from uuid import uuid4 @@ -53,12 +53,12 @@ def __init__(self, origin: str = None, tags: List[str] = None, create_time: date self.tags = tags or list() self.attributes = kwargs.get('attributes', None) or dict() self.event_type = kwargs.get('event_type', kwargs.get('type', None)) or 'Heartbeat' - self.create_time = create_time or datetime.utcnow() + self.create_time = create_time or datetime.now(UTC) self.timeout = timeout self.max_latency = max_latency - self.receive_time = kwargs.get('receive_time', None) or datetime.utcnow() + self.receive_time = kwargs.get('receive_time', None) or datetime.now(UTC) self.latency = int((self.receive_time - self.create_time).total_seconds() * 1000) - self.since = datetime.utcnow() - self.receive_time + self.since = datetime.now(UTC) - self.receive_time self.customer = customer @property @@ -137,9 +137,9 @@ def from_record(cls, rec) -> 'Heartbeat': tags=rec.tags, attributes=dict(getattr(rec, 'attributes') or ()), event_type=rec.type, - create_time=rec.create_time, + create_time=rec.create_time.replace(tzinfo=UTC) if rec.create_time is not None else None, timeout=rec.timeout, - receive_time=rec.receive_time, + receive_time=rec.receive_time.replace(tzinfo=UTC) if rec.receive_time is not None else None, latency=rec.latency, since=rec.since, customer=rec.customer diff --git a/alerta/models/history.py b/alerta/models/history.py index 43f012f0c..41ebf67d4 100644 --- a/alerta/models/history.py +++ b/alerta/models/history.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import UTC, datetime from alerta.utils.response import absolute_url @@ -13,7 +13,7 @@ def __init__(self, id, event, **kwargs): self.value = kwargs.get('value', None) self.text = kwargs.get('text', None) self.change_type = kwargs.get('change_type', kwargs.get('type', None)) or '' - self.update_time = kwargs.get('update_time', None) or datetime.utcnow() + self.update_time = kwargs.get('update_time', None) or datetime.now(UTC) self.user = kwargs.get('user', None) self.timeout = kwargs.get('timeout', None) diff --git a/alerta/models/key.py b/alerta/models/key.py index 53db4a1c3..80a79076d 100644 --- a/alerta/models/key.py +++ b/alerta/models/key.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import UTC, datetime, timedelta from typing import Any, Dict, List, Optional, Tuple, Union from uuid import uuid4 @@ -28,7 +28,7 @@ def __init__(self, user: str, scopes: List[str], text: str = '', expire_time: da self.user = user self.scopes = scopes or key_helper.user_default_scopes self.text = text - self.expire_time = expire_time or datetime.utcnow() + timedelta(days=key_helper.api_key_expire_days) + self.expire_time = expire_time or datetime.now(UTC) + timedelta(days=key_helper.api_key_expire_days) self.count = kwargs.get('count', 0) # type: ignore self.last_used_time = kwargs.get('last_used_time', None) self.customer = customer @@ -39,7 +39,7 @@ def type(self) -> str: @property def status(self) -> ApiKeyStatus: - return ApiKeyStatus.Expired if datetime.utcnow() > self.expire_time else ApiKeyStatus.Active + return ApiKeyStatus.Expired if datetime.now(UTC) > self.expire_time else ApiKeyStatus.Active @classmethod def parse(cls, json: JSON) -> 'ApiKey': @@ -120,7 +120,7 @@ def from_record(cls, rec) -> 'ApiKey': user=rec.user, scopes=[Scope(s) for s in rec.scopes], # legacy type => scopes conversion only required for mongo documents text=rec.text, - expire_time=rec.expire_time, + expire_time=rec.expire_time.replace(tzinfo=UTC) if rec.expire_time is not None else None, count=rec.count, last_used_time=rec.last_used_time, customer=rec.customer @@ -177,7 +177,7 @@ def delete(self) -> bool: @staticmethod def verify_key(key: str) -> Optional['ApiKey']: key_info = ApiKey.from_db(db.get_key(key)) - if key_info and key_info.expire_time > datetime.utcnow(): + if key_info and key_info.expire_time > datetime.now(UTC): db.update_key_last_used(key) return key_info return None diff --git a/alerta/models/note.py b/alerta/models/note.py index 7f7c5079e..0007bada5 100644 --- a/alerta/models/note.py +++ b/alerta/models/note.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import UTC, datetime from typing import Any, Dict, List, Optional, Tuple, Union from uuid import uuid4 @@ -22,7 +22,7 @@ def __init__(self, text: str, user: str, note_type: str, **kwargs) -> None: self.user = user self.note_type = note_type self.attributes = kwargs.get('attributes', None) or dict() - self.create_time = kwargs['create_time'] if 'create_time' in kwargs else datetime.utcnow() + self.create_time = kwargs['create_time'] if 'create_time' in kwargs else datetime.now(UTC) self.update_time = kwargs.get('update_time') self.alert = kwargs.get('alert') self.customer = kwargs.get('customer') diff --git a/alerta/models/notification_delay.py b/alerta/models/notification_delay.py index 4cb03d7b9..ab30ab067 100644 --- a/alerta/models/notification_delay.py +++ b/alerta/models/notification_delay.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import UTC, datetime from typing import Any, Dict, List, Optional, Tuple, Union from uuid import uuid4 @@ -72,7 +72,7 @@ def find_by_id(id) -> Optional['NotificationDelay']: @ staticmethod def find_firing() -> List['NotificationDelay']: - return [NotificationDelay.from_db(notification_delay) for notification_delay in db.get_delayed_notifications_firing(datetime.utcnow())] + return [NotificationDelay.from_db(notification_delay) for notification_delay in db.get_delayed_notifications_firing(datetime.now(UTC))] @ staticmethod def delete_alert(alert_id) -> List['NotificationDelay']: diff --git a/alerta/models/notification_history.py b/alerta/models/notification_history.py index ccba5f017..101bba7e3 100644 --- a/alerta/models/notification_history.py +++ b/alerta/models/notification_history.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import UTC, datetime from typing import Any, Dict, List, Optional, Tuple, Union from uuid import uuid4 @@ -13,7 +13,7 @@ class NotificationHistory: def __init__(self, sent: bool, message: str, channel: str, rule: str, alert: str, receiver: str, sender: str, **kwargs) -> None: self.id = kwargs.get('id') or str(uuid4()) self.sent = sent - self.sent_time = kwargs.get('sent_time', datetime.utcnow()) + self.sent_time = kwargs.get('sent_time', datetime.now(UTC)) self.message = message self.channel = channel self.rule = rule diff --git a/alerta/models/notification_rule.py b/alerta/models/notification_rule.py index 8f060bc4f..2854ac553 100644 --- a/alerta/models/notification_rule.py +++ b/alerta/models/notification_rule.py @@ -1,4 +1,4 @@ -from datetime import datetime, time, timedelta +from datetime import UTC, datetime, time, timedelta from typing import Any, Dict, List, Optional, Tuple, Union from uuid import uuid4 @@ -188,7 +188,7 @@ def __init__( self.user = kwargs.get('user', None) self.create_time = ( - kwargs['create_time'] if 'create_time' in kwargs else datetime.utcnow() + kwargs['create_time'] if 'create_time' in kwargs else datetime.now(UTC) ) self.text = kwargs.get('text', None) @@ -464,7 +464,7 @@ def find_all_active_status(alert: 'Alert', status: str) -> 'list[NotificationRul @staticmethod def find_all_reactivate(**kwargs) -> 'list[NotificationRule]': - now = datetime.utcnow() + now = datetime.now(UTC) return [NotificationRule.from_db(db_notification_rule) for db_notification_rule in db.get_notification_rules_reactivate(now)] def get_notification_rule_alerts(self, page=None, page_size=None): diff --git a/alerta/models/on_call.py b/alerta/models/on_call.py index c174806c3..4dbd027ce 100644 --- a/alerta/models/on_call.py +++ b/alerta/models/on_call.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import UTC, datetime from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union from uuid import uuid4 @@ -32,7 +32,7 @@ def __init__(self, **kwargs) -> None: self.customer = kwargs.get('customer') self.user = kwargs.get('user') - self.create_time = kwargs['create_time'] if 'create_time' in kwargs else datetime.utcnow() + self.create_time = kwargs['create_time'] if 'create_time' in kwargs else datetime.now(UTC) @property def users(self): diff --git a/alerta/models/user.py b/alerta/models/user.py index 18896388b..73cd2ee54 100644 --- a/alerta/models/user.py +++ b/alerta/models/user.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import UTC, datetime from typing import Any, Dict, List, Optional, Tuple, Union from uuid import uuid4 @@ -54,10 +54,10 @@ def __init__(self, name: str, login: str, password: str, email: str, roles: List self.status = kwargs.get('status', None) or UserStatus.Active self.roles = current_app.config['ADMIN_ROLES'] if self.email and self.email in current_app.config['ADMIN_USERS'] else roles self.attributes = kwargs.get('attributes', None) or dict() - self.create_time = kwargs.get('create_time', None) or datetime.utcnow() + self.create_time = kwargs.get('create_time', None) or datetime.now(UTC) self.last_login = kwargs.get('last_login', None) self.text = text or '' - self.update_time = kwargs.get('update_time', None) or datetime.utcnow() + self.update_time = kwargs.get('update_time', None) or datetime.now(UTC) self.email_verified = kwargs.get('email_verified', False) @property diff --git a/alerta/plugins/notification_rule.py b/alerta/plugins/notification_rule.py index 32e0b8126..566b2791f 100644 --- a/alerta/plugins/notification_rule.py +++ b/alerta/plugins/notification_rule.py @@ -1,7 +1,7 @@ import json import logging import smtplib -from datetime import datetime, timedelta +from datetime import UTC, datetime, timedelta from threading import Thread import requests @@ -185,7 +185,7 @@ def log_notification(sent: bool, message: str, channel: NotificationChannel, rul def delay_notification(alert: Alert, notification_rule: NotificationRule): - delay_time = datetime.utcnow() + notification_rule.delay_time + delay_time = datetime.now(UTC) + notification_rule.delay_time NotificationDelay.parse({ 'alert_id': alert.id, 'notification_rule_id': notification_rule.id, diff --git a/alerta/utils/audit.py b/alerta/utils/audit.py index b2511472e..21acb2e35 100644 --- a/alerta/utils/audit.py +++ b/alerta/utils/audit.py @@ -1,6 +1,6 @@ import json import uuid -from datetime import datetime +from datetime import UTC, datetime from typing import Any, List import blinker @@ -91,7 +91,7 @@ def get_redacted_data(r): return json.dumps({ 'id': str(uuid.uuid4()), - '@timestamp': datetime.utcnow(), + '@timestamp': datetime.now(UTC), 'event': event, 'category': category, 'message': message, diff --git a/alerta/utils/client.py b/alerta/utils/client.py index c6a9c226c..cef0302f2 100644 --- a/alerta/utils/client.py +++ b/alerta/utils/client.py @@ -41,7 +41,7 @@ def send_alert(self, resource, event, **kwargs): 'attributes': kwargs.get('attributes', None) or dict(), 'origin': kwargs.get('origin'), 'type': kwargs.get('type'), - 'createTime': datetime.datetime.utcnow(), + 'createTime': datetime.datetime.now(datetime.UTC), 'timeout': kwargs.get('timeout'), 'rawData': kwargs.get('raw_data'), 'customer': kwargs.get('customer') diff --git a/alerta/utils/format.py b/alerta/utils/format.py index bf2f81806..7508cceaa 100644 --- a/alerta/utils/format.py +++ b/alerta/utils/format.py @@ -56,7 +56,7 @@ def parse(date_str: str) -> Optional[dt]: if not isinstance(date_str, str): return None try: - return datetime.datetime.strptime(date_str, '%Y-%m-%dT%H:%M:%S.%fZ') + return datetime.datetime.strptime(date_str, '%Y-%m-%dT%H:%M:%S.%fZ').replace(tzinfo=datetime.UTC) except Exception: raise ValueError('dates must be ISO 8601 date format YYYY-MM-DDThh:mm:ss.sssZ') diff --git a/alerta/views/alerts.py b/alerta/views/alerts.py index 600709b06..773671d27 100644 --- a/alerta/views/alerts.py +++ b/alerta/views/alerts.py @@ -1,5 +1,5 @@ import csv -from datetime import datetime +from datetime import UTC, datetime from io import BytesIO, StringIO from flask import current_app, g, jsonify, request, send_file @@ -411,7 +411,7 @@ def delete_alerts(): @timer(gets_timer) @jsonp def search_alerts(): - query_time = datetime.utcnow() + query_time = datetime.now(UTC) query = qb.alerts.from_params(request.args, customers=g.customers, query_time=query_time) show_raw_data = request.args.get('show-raw-data', default=False, type=lambda x: x.lower() in ['true', 't', '1', 'yes', 'y', 'on']) show_history = request.args.get('show-history', default=False, type=lambda x: x.lower() in ['true', 't', '1', 'yes', 'y', 'on']) diff --git a/tests/test_blackouts.py b/tests/test_blackouts.py index a6a939a7f..24dd68087 100644 --- a/tests/test_blackouts.py +++ b/tests/test_blackouts.py @@ -2,7 +2,7 @@ import os import time import unittest -from datetime import datetime, timedelta +from datetime import UTC, datetime, timedelta from alerta.app import create_app, db, plugins from alerta.exceptions import BlackoutPeriod @@ -685,7 +685,7 @@ def test_custom_notify(self): } # create new blackout with end time 1 second in the future - three_second_from_now = datetime.utcnow() + timedelta(seconds=3) + three_second_from_now = datetime.now(UTC) + timedelta(seconds=3) blackout = { 'environment': 'Production', 'service': ['Core'], From 3d1fa3cd3f5d75a5132dade6b21c958248f7e889 Mon Sep 17 00:00:00 2001 From: sbgap Date: Fri, 6 Feb 2026 13:47:22 +0100 Subject: [PATCH 2/3] tests: remove python 3.10 from tests --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1906e588f..b45caca29 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -91,7 +91,7 @@ jobs: strategy: matrix: - python-version: ['3.10', '3.12'] + python-version: ['3.12'] steps: - uses: actions/checkout@v4 From 8d75072325841af1620931b2223c431858970f21 Mon Sep 17 00:00:00 2001 From: sbgap Date: Fri, 6 Feb 2026 13:51:00 +0100 Subject: [PATCH 3/3] tests: update python 3.10 in integration test to 3.12 --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b45caca29..118bc60ea 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -174,10 +174,10 @@ jobs: libldap2-dev \ libsasl2-dev \ xmlsec1 - - name: Set up Python 3.10 + - name: Set up Python 3.12 uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: '3.12' architecture: 'x64' - name: Install dependencies id: install-deps