From 871816adae839778947473d86c5422b5cbf4710b Mon Sep 17 00:00:00 2001 From: David Cain Date: Thu, 14 Jul 2022 21:17:19 -0700 Subject: [PATCH] Replace use of pytz with built-in timezone Now that we're on Python 3.10, we can just use the built-in `timezone` module instead of installing `pytz`. `pytz` remains a Django dependency (for now), but we at least can remove our direct dependence on it. --- poetry.lock | 2 +- pyproject.toml | 2 -- ws/settings.py | 2 +- ws/tests/factories.py | 5 ++--- ws/tests/test_merge.py | 7 +++---- ws/tests/test_tasks.py | 11 +++++------ ws/tests/utils/test_dates.py | 19 +++++++++---------- ws/tests/views/test_account.py | 10 +++++----- ws/tests/views/test_participant.py | 4 ++-- ws/utils/dates.py | 3 +-- 10 files changed, 29 insertions(+), 36 deletions(-) diff --git a/poetry.lock b/poetry.lock index b753263d..ee30b9c9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1294,7 +1294,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [metadata] lock-version = "1.1" python-versions = "^3.10.0" -content-hash = "fe973379b5807012e7e1c24a1f6e586a8e9cd5bc30578e453be59686778812af" +content-hash = "25f8989b51f8cfdfc3c6f5a46626eeca60d100e46b039b34bb98e7f1241b848f" [metadata.files] amqp = [] diff --git a/pyproject.toml b/pyproject.toml index b66405f7..69c9d612 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,6 @@ freezegun = "*" isort = { version = "^5.8", extras = ["pyproject"] } # Version 5 introduces black compatibility lxml = "*" pylint = "^2.8" -pytz = "*" # (It's a production dependency by way of Django, but used directly in tests) responses = "*" # Dependencies for static type checking @@ -58,7 +57,6 @@ django-stubs = { version = "^1.9" } mypy = { version = "*"} types-certifi = { version = "*" } types-cryptography = { version = "*" } -types-pytz = { version = "*" } types-requests = { version = "*" } # Dependencies helpful for local development, not used otherwise diff --git a/ws/settings.py b/ws/settings.py index af048328..0d8dc080 100644 --- a/ws/settings.py +++ b/ws/settings.py @@ -2,7 +2,7 @@ Django settings for ws project. For more information on this file, see -https://docs.djangoproject.com/en/2.2/topics/settings/ +https://docs.djangoproject.com/en/3.2/topics/settings/ """ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) diff --git a/ws/tests/factories.py b/ws/tests/factories.py index 30018124..20d7d254 100644 --- a/ws/tests/factories.py +++ b/ws/tests/factories.py @@ -1,9 +1,8 @@ -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone import allauth.account.models as account_models import factory import factory.fuzzy -import pytz from factory.django import DjangoModelFactory from mitoc_const import affiliations @@ -138,7 +137,7 @@ class Meta: participant = factory.SubFactory(ParticipantFactory) reminder_sent_at = factory.fuzzy.FuzzyDateTime( - start_dt=datetime(2021, 11, 13, tzinfo=pytz.UTC) + start_dt=datetime(2021, 11, 13, tzinfo=timezone.utc) ) diff --git a/ws/tests/test_merge.py b/ws/tests/test_merge.py index bb924920..4f0d50e7 100644 --- a/ws/tests/test_merge.py +++ b/ws/tests/test_merge.py @@ -1,7 +1,6 @@ import unittest -from datetime import datetime +from datetime import datetime, timezone -import pytz from django.contrib.auth.models import Permission, User from django.db import connections from django.db.utils import IntegrityError @@ -145,10 +144,10 @@ def test_password_quality(self): def test_membership_reminders(self): """The newest reminder is honored, even if delivered to the older participant.""" - newer_reminder_sent_at = datetime(2020, 12, 25, tzinfo=pytz.UTC) + newer_reminder_sent_at = datetime(2020, 12, 25, tzinfo=timezone.utc) factories.MembershipReminderFactory.create( participant=self.tim, - reminder_sent_at=datetime(2020, 10, 1, tzinfo=pytz.UTC), + reminder_sent_at=datetime(2020, 10, 1, tzinfo=timezone.utc), ) factories.MembershipReminderFactory.create( participant=self.old, diff --git a/ws/tests/test_tasks.py b/ws/tests/test_tasks.py index e2b7d9c6..3254aecb 100644 --- a/ws/tests/test_tasks.py +++ b/ws/tests/test_tasks.py @@ -1,8 +1,7 @@ -from datetime import date, datetime +from datetime import date, datetime, timezone from unittest import mock from unittest.mock import patch -import pytz from django.core import mail from django.core.cache import cache from django.test import SimpleTestCase @@ -246,7 +245,7 @@ def test_nobody_needs_reminding(): ) factories.MembershipReminderFactory.create( participant=already_reminded, - reminder_sent_at=datetime(2020, 12, 25, tzinfo=pytz.UTC), + reminder_sent_at=datetime(2020, 12, 25, tzinfo=timezone.utc), ) with patch.object(tasks.remind_lapsed_participant_to_renew, 'delay') as email: @@ -272,7 +271,7 @@ def test_can_be_reminded_once_a_year(): # We reminded them once before, but it was for the previous year's membership factories.MembershipReminderFactory.create( participant=par, - reminder_sent_at=datetime(2017, 12, 25, tzinfo=pytz.UTC), + reminder_sent_at=datetime(2017, 12, 25, tzinfo=timezone.utc), ) with patch.object(tasks.remind_lapsed_participant_to_renew, 'delay') as email: tasks.remind_participants_to_renew() @@ -298,7 +297,7 @@ def test_success(self): ) self.assertEqual(reminder.participant, par) self.assertEqual( - reminder.reminder_sent_at, datetime(2019, 1, 25, 17, 0, tzinfo=pytz.UTC) + reminder.reminder_sent_at, datetime(2019, 1, 25, 17, 0, tzinfo=timezone.utc) ) def test_idempotent(self): @@ -336,7 +335,7 @@ def test_tried_to_remind_again_too_soon(self): ) factories.MembershipReminderFactory.create( participant=par, - reminder_sent_at=datetime(2018, 4, 25, tzinfo=pytz.UTC), + reminder_sent_at=datetime(2018, 4, 25, tzinfo=timezone.utc), ) with patch.object(renew, 'send_email_reminding_to_renew') as email: diff --git a/ws/tests/utils/test_dates.py b/ws/tests/utils/test_dates.py index 8bfadc68..21116478 100644 --- a/ws/tests/utils/test_dates.py +++ b/ws/tests/utils/test_dates.py @@ -2,7 +2,6 @@ from unittest import mock from zoneinfo import ZoneInfo -import pytz from freezegun import freeze_time from ws import enums @@ -123,27 +122,27 @@ def test_closest_wednesday(self, local_now): self.assertLess(test_date, closest_wed) def test_closest_wed_at_noon(self): - est = pytz.timezone('America/New_York') + eastern = ZoneInfo('America/New_York') # On Wednesday, we return the same day with freeze_time("2016-12-28 23:22 EST"): self.assertEqual( date_utils.closest_wed_at_noon(), - est.localize(datetime(2016, 12, 28, 12, 0, 0)), + datetime(2016, 12, 28, 12, 0, 0, tzinfo=eastern), ) # 3 days to the next Wednesday, 4 days to the previous with freeze_time("2016-12-25 10:00 EST"): self.assertEqual( date_utils.closest_wed_at_noon(), - est.localize(datetime(2016, 12, 28, 12, 0, 0)), + datetime(2016, 12, 28, 12, 0, 0, tzinfo=eastern), ) # 4 days to the next Wednesday, 3 days to the previous with freeze_time("2016-12-24 10:00 EST"): self.assertEqual( date_utils.closest_wed_at_noon(), - est.localize(datetime(2016, 12, 21, 12, 0, 0)), + datetime(2016, 12, 21, 12, 0, 0, tzinfo=eastern), ) def test_is_currently_iap(self): @@ -165,8 +164,8 @@ def test_is_currently_iap(self): self.assertFalse(date_utils.is_currently_iap()) def test_fcfs_close_time(self): - est = pytz.timezone('America/New_York') - thur_night = est.localize(datetime(2019, 1, 24, 23, 59, 59)) + eastern = ZoneInfo("America/New_York") + thur_night = datetime(2019, 1, 24, 23, 59, 59, tzinfo=eastern) # For the usual case (trips over the weekend), it's the Thursday beforehand (fri, sat, sun, mon) = (date(2019, 1, dom) for dom in [25, 26, 27, 28]) @@ -187,9 +186,9 @@ def test_fcfs_close_time(self): self.assertEqual(date_utils.fcfs_close_time(date(2019, 1, 24)), wed_night) def test_next_lottery(self): - est = pytz.timezone('America/New_York') - friday_the_23rd = est.localize(datetime(2019, 1, 23, 9, 0, 0)) - friday_the_30th = est.localize(datetime(2019, 1, 30, 9, 0, 0)) + eastern = ZoneInfo("America/New_York") + friday_the_23rd = datetime(2019, 1, 23, 9, 0, 0, tzinfo=eastern) + friday_the_30th = datetime(2019, 1, 30, 9, 0, 0, tzinfo=eastern) # Normal cases: lottery is always just the next Wednesday morning with freeze_time("Fri, 25 Jan 2019 03:00:00 EST"): diff --git a/ws/tests/views/test_account.py b/ws/tests/views/test_account.py index 4c5aa226..be80a42e 100644 --- a/ws/tests/views/test_account.py +++ b/ws/tests/views/test_account.py @@ -1,5 +1,5 @@ import uuid -from datetime import datetime +from datetime import datetime, timezone from unittest import mock import pytz @@ -73,7 +73,7 @@ def test_known_bad_password(self, mocked_settings): self.assertFalse(quality.is_insecure) self.assertEqual( quality.last_checked, - datetime(2019, 8, 29, 16, 25, tzinfo=pytz.utc), + datetime(2019, 8, 29, 16, 25, tzinfo=timezone.utc), ) # Log in again, but this time with DEBUG off (& whitelist still present) @@ -111,7 +111,7 @@ def test_previously_secure_password(self): self.assertTrue(quality.is_insecure) self.assertEqual( quality.last_checked, - datetime(2019, 8, 29, 16, 25, tzinfo=pytz.utc), + datetime(2019, 8, 29, 16, 25, tzinfo=timezone.utc), ) # Because the user's password is insecure, they're prompted to change it @@ -162,7 +162,7 @@ def test_password_is_marked_secure(self): self.assertFalse(participant.passwordquality.is_insecure) self.assertEqual( participant.passwordquality.last_checked, - datetime(2019, 8, 29, 16, 25, tzinfo=pytz.utc), + datetime(2019, 8, 29, 16, 25, tzinfo=timezone.utc), ) def test_redirect_should_be_preserved(self): @@ -218,7 +218,7 @@ def test_change_password_fram_insecure(self): self.assertFalse(par.passwordquality.is_insecure) self.assertEqual( par.passwordquality.last_checked, - datetime(2019, 7, 15, 16, 45, tzinfo=pytz.utc), + datetime(2019, 7, 15, 16, 45, tzinfo=timezone.utc), ) def test_user_without_participant(self): diff --git a/ws/tests/views/test_participant.py b/ws/tests/views/test_participant.py index 839324ff..85532ba9 100644 --- a/ws/tests/views/test_participant.py +++ b/ws/tests/views/test_participant.py @@ -1,5 +1,5 @@ import random -from datetime import date, datetime +from datetime import date, datetime, timezone from unittest import mock import pytz @@ -317,7 +317,7 @@ def test_new_participant(self): task_update.delay.assert_called_with(participant.pk) # We then update the timestamps! - now = datetime(2019, 2, 15, 17, 25, tzinfo=pytz.utc) + now = datetime(2019, 2, 15, 17, 25, tzinfo=timezone.utc) self.assertEqual(participant.last_updated, now) # Since the participant modified their own profile, we save `profile_last_updated` self.assertEqual(participant.profile_last_updated, now) diff --git a/ws/utils/dates.py b/ws/utils/dates.py index 20747d1b..0c831f8e 100644 --- a/ws/utils/dates.py +++ b/ws/utils/dates.py @@ -16,8 +16,7 @@ def localize(dt_time: datetime) -> datetime: >>> localize(datetime(2018, 10, 27, 4, 30) datetime.datetime(2018, 10, 27, 4, 30, tzinfo=) """ - pytz_timezone = timezone.get_default_timezone() - return pytz_timezone.localize(dt_time) + return timezone.get_default_timezone().localize(dt_time) def itinerary_available_at(trip_date: date) -> datetime: