From 95d975f33961ba0be80a09c446faec479e3db27e Mon Sep 17 00:00:00 2001 From: "Chandan Kumar (raukadah)" Date: Tue, 5 Aug 2025 23:09:29 +0530 Subject: [PATCH] Replace dateutils usage with datetime and oslo.utils This cr fixes: * Replaced ``dateutil.tz.tzlocal()`` and ``dateutil.tz.tzutc()`` with ``datetime.timezone`` built-in classes in audit controllers and continuous audit scheduling. * Replaced ``dateutil.parser.parse()`` with ``oslo_utils.timeutils.parse_isotime()`` in the zone migration strategy for parsing datetime strings. Closes-Bug: #2118404 Change-Id: I6d8a345fa4339a688769b147413dcdf3016bf4a0 Signed-off-by: Chandan Kumar (raukadah) --- ...til-dependency-2118404-f5a8b2c1e9d4a6b3.yaml | 5 +++++ watcher/api/controllers/v1/audit.py | 17 +++++++---------- watcher/decision_engine/audit/continuous.py | 4 ++-- .../strategy/strategies/zone_migration.py | 9 +++++---- watcher/tests/api/v1/test_audits.py | 8 +++----- 5 files changed, 22 insertions(+), 21 deletions(-) create mode 100644 releasenotes/notes/drop-python-dateutil-dependency-2118404-f5a8b2c1e9d4a6b3.yaml diff --git a/releasenotes/notes/drop-python-dateutil-dependency-2118404-f5a8b2c1e9d4a6b3.yaml b/releasenotes/notes/drop-python-dateutil-dependency-2118404-f5a8b2c1e9d4a6b3.yaml new file mode 100644 index 000000000..64f75e03a --- /dev/null +++ b/releasenotes/notes/drop-python-dateutil-dependency-2118404-f5a8b2c1e9d4a6b3.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Removed the ``python-dateutil`` dependency from Watcher to reduce the + number of external dependencies and improve maintainability. diff --git a/watcher/api/controllers/v1/audit.py b/watcher/api/controllers/v1/audit.py index e58b26b57..4e59b17aa 100644 --- a/watcher/api/controllers/v1/audit.py +++ b/watcher/api/controllers/v1/audit.py @@ -30,7 +30,7 @@ states, visit :ref:`the Audit State machine `. """ import datetime -from dateutil import tz +from datetime import timezone from http import HTTPStatus import jsonschema @@ -635,13 +635,11 @@ class AuditsController(rest.RestController): start_time_value = audit_dict.get('start_time') end_time_value = audit_dict.get('end_time') if start_time_value: - audit_dict['start_time'] = start_time_value.replace( - tzinfo=tz.tzlocal()).astimezone( - tz.tzutc()).replace(tzinfo=None) + audit_dict['start_time'] = start_time_value.astimezone( + timezone.utc).replace(tzinfo=None) if end_time_value: - audit_dict['end_time'] = end_time_value.replace( - tzinfo=tz.tzlocal()).astimezone( - tz.tzutc()).replace(tzinfo=None) + audit_dict['end_time'] = end_time_value.astimezone( + timezone.utc).replace(tzinfo=None) new_audit = objects.Audit(context, **audit_dict) new_audit.create() @@ -688,9 +686,8 @@ class AuditsController(rest.RestController): patch_value = api_utils.get_patch_value(patch, patch_path) # convert string format to UTC time new_patch_value = wutils.parse_isodatetime( - patch_value).replace( - tzinfo=tz.tzlocal()).astimezone( - tz.tzutc()).replace(tzinfo=None) + patch_value).astimezone( + timezone.utc).replace(tzinfo=None) api_utils.set_patch_value(patch, patch_path, new_patch_value) audit = Audit(**api_utils.apply_jsonpatch(audit_dict, patch)) diff --git a/watcher/decision_engine/audit/continuous.py b/watcher/decision_engine/audit/continuous.py index e9ae1bec1..ebb0eb6dd 100644 --- a/watcher/decision_engine/audit/continuous.py +++ b/watcher/decision_engine/audit/continuous.py @@ -21,7 +21,7 @@ import datetime from croniter import croniter -from dateutil import tz +from datetime import timezone from oslo_utils import timeutils from watcher.common import context @@ -123,7 +123,7 @@ class ContinuousAuditHandler(base.AuditHandler): 'next_run_time') else 'run_date' # We should convert UTC time to local time without tzinfo trigger_args[time_var] = trigger_args[time_var].replace( - tzinfo=tz.tzutc()).astimezone(tz.tzlocal()).replace(tzinfo=None) + tzinfo=timezone.utc).astimezone().replace(tzinfo=None) self.scheduler.add_job(self.execute_audit, trigger, args=[audit, audit_context], name='execute_audit', diff --git a/watcher/decision_engine/strategy/strategies/zone_migration.py b/watcher/decision_engine/strategy/strategies/zone_migration.py index 5b6e42888..74eee043b 100644 --- a/watcher/decision_engine/strategy/strategies/zone_migration.py +++ b/watcher/decision_engine/strategy/strategies/zone_migration.py @@ -12,9 +12,8 @@ # limitations under the License. # -from dateutil.parser import parse - from oslo_log import log +from oslo_utils import timeutils from cinderclient.v3.volumes import Volume from novaclient.v2.servers import Server @@ -893,7 +892,8 @@ class ComputeSpecSortFilter(BaseFilter): reverse=True) elif sort_key == 'created_at': result = sorted(items, - key=lambda x: parse(getattr(x, sort_key)), + key=lambda x: timeutils.parse_isotime( + getattr(x, sort_key)), reverse=False) return result @@ -959,7 +959,8 @@ class StorageSpecSortFilter(BaseFilter): if sort_key == 'created_at': result = sorted(items, - key=lambda x: parse(getattr(x, sort_key)), + key=lambda x: timeutils.parse_isotime( + getattr(x, sort_key)), reverse=False) else: result = sorted(items, diff --git a/watcher/tests/api/v1/test_audits.py b/watcher/tests/api/v1/test_audits.py index 107c2dc6c..5ce93075a 100644 --- a/watcher/tests/api/v1/test_audits.py +++ b/watcher/tests/api/v1/test_audits.py @@ -11,7 +11,7 @@ # limitations under the License. import datetime -from dateutil import tz +from datetime import timezone import itertools from unittest import mock from urllib import parse as urlparse @@ -942,10 +942,8 @@ class TestPost(api_base.FunctionalTest): response.json['start_time']) return_end_time = timeutils.parse_isotime( response.json['end_time']) - iso_start_time = start_time.replace( - tzinfo=tz.tzlocal()).astimezone(tz.tzutc()) - iso_end_time = end_time.replace( - tzinfo=tz.tzlocal()).astimezone(tz.tzutc()) + iso_start_time = start_time.astimezone(timezone.utc) + iso_end_time = end_time.astimezone(timezone.utc) self.assertEqual(iso_start_time, return_start_time) self.assertEqual(iso_end_time, return_end_time)