From ccce36d7c76105003531f7dd716c2a1f1c03678d Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Wed, 16 Apr 2025 17:46:01 -0300 Subject: [PATCH 01/16] refactor: smtpd -> aiosmtpd --- ietf/utils/test_runner.py | 2 +- ietf/utils/test_smtpserver.py | 111 +++++++++++----------------------- requirements.txt | 1 + 3 files changed, 37 insertions(+), 77 deletions(-) diff --git a/ietf/utils/test_runner.py b/ietf/utils/test_runner.py index a77377ffb5..c06e7876db 100644 --- a/ietf/utils/test_runner.py +++ b/ietf/utils/test_runner.py @@ -863,7 +863,7 @@ def setup_test_environment(self, **kwargs): try: # remember the value so ietf.utils.mail.send_smtp() will use the same ietf.utils.mail.SMTP_ADDR['port'] = base + offset - self.smtpd_driver = SMTPTestServerDriver((ietf.utils.mail.SMTP_ADDR['ip4'],ietf.utils.mail.SMTP_ADDR['port']),None) + self.smtpd_driver = SMTPTestServerDriver(ietf.utils.mail.SMTP_ADDR['ip4'],ietf.utils.mail.SMTP_ADDR['port'], None) self.smtpd_driver.start() print((" Running an SMTP test server on %(ip4)s:%(port)s to catch outgoing email." % ietf.utils.mail.SMTP_ADDR)) break diff --git a/ietf/utils/test_smtpserver.py b/ietf/utils/test_smtpserver.py index 66675aa0b1..18390df726 100644 --- a/ietf/utils/test_smtpserver.py +++ b/ietf/utils/test_smtpserver.py @@ -1,92 +1,51 @@ -# Copyright The IETF Trust 2014-2020, All Rights Reserved +# Copyright The IETF Trust 2014-2025, All Rights Reserved # -*- coding: utf-8 -*- +from aiosmtpd.controller import Controller +from email.utils import parseaddr +from typing import Optional -import smtpd -import threading -import asyncore -import debug # pyflakes:ignore +class SMTPTestHandler: -class AsyncCoreLoopThread(object): + def __init__(self, inbox: list): + self.inbox = inbox - def wrap_loop(self, exit_condition, timeout=1.0, use_poll=False, map=None): - if map is None: - map = asyncore.socket_map - while map and not exit_condition: - asyncore.loop(timeout=1.0, use_poll=False, map=map, count=1) + async def handle_DATA(self, server, session, envelope): + """Handle the DATA command and 'deliver' the message""" - def start(self): - """Start the listening service""" - self.exit_condition = [] - kwargs={'exit_condition':self.exit_condition,'timeout':1.0} - self.thread = threading.Thread(target=self.wrap_loop, kwargs=kwargs) - self.thread.daemon = True - self.thread.daemon = True - self.thread.start() - - def stop(self): - """Stop the listening service""" - self.exit_condition.append(True) - self.thread.join() - - -class SMTPTestChannel(smtpd.SMTPChannel): + self.inbox.append(envelope.content) + # Per RFC2033: https://datatracker.ietf.org/doc/html/rfc2033.html#section-4.2 + # ...after the final ".", the server returns one reply + # for each previously successful RCPT command in the mail transaction, + # in the order that the RCPT commands were issued. Even if there were + # multiple successful RCPT commands giving the same forward-path, there + # must be one reply for each successful RCPT command. + return "\n".join("250 OK" for _ in envelope.rcpt_tos) -# mail_options = ['BODY=8BITMIME', 'SMTPUTF8'] - - def smtp_RCPT(self, arg): - if not self.mailfrom: - self.push(str('503 Error: need MAIL command')) - return - arg = self._strip_command_keyword('TO:', arg) - address, __ = self._getaddr(arg) - if not address: - self.push(str('501 Syntax: RCPT TO:
')) - return + async def handle_RCPT(self, server, session, envelope, address, rcpt_options): + """Handle an RCPT command and add the address to the envelope if it is acceptable""" + _, address = parseaddr(address) + if address == "": + return "501 Syntax: RCPT TO:
" if "poison" in address: - self.push(str('550 Error: Not touching that')) - return - self.rcpt_options = [] - self.rcpttos.append(address) - self.push(str('250 Ok')) - -class SMTPTestServer(smtpd.SMTPServer): - - def __init__(self,localaddr,remoteaddr,inbox): - if inbox is not None: - self.inbox=inbox - else: - self.inbox = [] - smtpd.SMTPServer.__init__(self,localaddr,remoteaddr) + return "550 Error: Not touching that" + # At this point the address is acceptable + envelope.rcpt_tos.append(address) + return "250 OK" - def handle_accept(self): - pair = self.accept() - if pair is not None: - conn, addr = pair - #channel = SMTPTestChannel(self, conn, addr) - SMTPTestChannel(self, conn, addr) - def process_message(self, peer, mailfrom, rcpttos, data, mail_options=None, rcpt_options=None): - self.inbox.append(data) +class SMTPTestServerDriver: - -class SMTPTestServerDriver(object): - def __init__(self, localaddr, remoteaddr, inbox=None): - self.localaddr=localaddr - self.remoteaddr=remoteaddr - if inbox is not None: - self.inbox = inbox - else: - self.inbox = [] - self.thread_driver = None + def __init__(self, address: str, port: int, inbox: Optional[list] = None): + self.controller = Controller( + hostname=address, + port=port, + handler=SMTPTestHandler(inbox=[] if inbox is None else inbox), + ) def start(self): - self.smtpserver = SMTPTestServer(self.localaddr,self.remoteaddr,self.inbox) - self.thread_driver = AsyncCoreLoopThread() - self.thread_driver.start() + self.controller.start() def stop(self): - if self.thread_driver: - self.thread_driver.stop() - + self.controller.stop() diff --git a/requirements.txt b/requirements.txt index cd93f448e2..b00a21ab49 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ # -*- conf-mode -*- setuptools>=51.1.0 # Require this first, to prevent later errors # +aiosmtpd>=1.4.6 argon2-cffi>=21.3.0 # For the Argon2 password hasher option beautifulsoup4>=4.11.1 # Only used in tests bibtexparser>=1.2.0 # Only used in tests From 025878a1edc2441acea05681918f12d6568697bd Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Wed, 16 Apr 2025 17:55:25 -0300 Subject: [PATCH 02/16] test: set mock return value for EmailOnFailureCommandTests The test has been working, but in a broken way, for as long as it has existed. The smtpd-based test_smtpserver was masking an exception that did not interfere with the test's effectiveness. --- ietf/utils/management/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ietf/utils/management/tests.py b/ietf/utils/management/tests.py index e94c39354f..d704999cd1 100644 --- a/ietf/utils/management/tests.py +++ b/ietf/utils/management/tests.py @@ -12,7 +12,7 @@ from ietf.utils.test_utils import TestCase -@mock.patch.object(EmailOnFailureCommand, 'handle') +@mock.patch.object(EmailOnFailureCommand, 'handle', return_value=None) class EmailOnFailureCommandTests(TestCase): def test_calls_handle(self, handle_method): call_command(EmailOnFailureCommand()) From 5bb37009a7750e93222352f2cc7bde8a0a92bad8 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Wed, 16 Apr 2025 19:51:06 -0300 Subject: [PATCH 03/16] test: increase SMTP.line_length_limit --- ietf/utils/test_smtpserver.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ietf/utils/test_smtpserver.py b/ietf/utils/test_smtpserver.py index 18390df726..40da758d66 100644 --- a/ietf/utils/test_smtpserver.py +++ b/ietf/utils/test_smtpserver.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- from aiosmtpd.controller import Controller +from aiosmtpd.smtp import SMTP from email.utils import parseaddr from typing import Optional @@ -38,6 +39,10 @@ async def handle_RCPT(self, server, session, envelope, address, rcpt_options): class SMTPTestServerDriver: def __init__(self, address: str, port: int, inbox: Optional[list] = None): + # Allow longer lines than the 1001 that RFC 5321 requires. As of 2025-04-16 the + # datatracker emits some non-compliant messages. + # See https://aiosmtpd.aio-libs.org/en/latest/smtp.html + SMTP.line_length_limit = 4000 # tests start failing between 3000 and 4000 self.controller = Controller( hostname=address, port=port, From a0440de28b9d2e1496a6cbbabc01b9282a5e4b9f Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Thu, 17 Apr 2025 10:40:16 -0300 Subject: [PATCH 04/16] chore: suppress known deprecation warnings --- ietf/settings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ietf/settings.py b/ietf/settings.py index 33a2f976d9..c9c4fa37ad 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -26,6 +26,9 @@ warnings.filterwarnings("ignore", message="Report.file_reporters will no longer be available in Coverage.py 4.2", module="coverage.report") warnings.filterwarnings("ignore", message="Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated", module="bleach") warnings.filterwarnings("ignore", message="HTTPResponse.getheader\\(\\) is deprecated", module='selenium.webdriver') +warnings.filterwarnings("ignore", message="datetime.datetime.utcnow\\(\\) is deprecated", module="botocore.auth") +warnings.filterwarnings("ignore", message="currentThread\\(\\) is deprecated", module="coverage.pytracer") +warnings.filterwarnings("ignore", message="datetime.datetime.utcfromtimestamp\\(\\) is deprecated", module="pytz.tzinfo") BASE_DIR = os.path.dirname(os.path.abspath(__file__)) sys.path.append(os.path.abspath(BASE_DIR + "/..")) From 00d35e05fd3b36a3ab4f80a2f77043196fb1f18d Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Thu, 17 Apr 2025 10:57:07 -0300 Subject: [PATCH 05/16] refactor: utcfromtimestamp->fromtimestamp --- ietf/doc/views_stats.py | 2 +- ietf/settings.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ietf/doc/views_stats.py b/ietf/doc/views_stats.py index 0bbf0b91c5..78e3a1ca52 100644 --- a/ietf/doc/views_stats.py +++ b/ietf/doc/views_stats.py @@ -18,7 +18,7 @@ from ietf.utils.timezone import date_today -epochday = datetime.datetime.utcfromtimestamp(0).date().toordinal() +epochday = datetime.datetime.fromtimestamp(0, datetime.UTC).date().toordinal() def dt(s): diff --git a/ietf/settings.py b/ietf/settings.py index c9c4fa37ad..a5e3315904 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -29,6 +29,7 @@ warnings.filterwarnings("ignore", message="datetime.datetime.utcnow\\(\\) is deprecated", module="botocore.auth") warnings.filterwarnings("ignore", message="currentThread\\(\\) is deprecated", module="coverage.pytracer") warnings.filterwarnings("ignore", message="datetime.datetime.utcfromtimestamp\\(\\) is deprecated", module="pytz.tzinfo") +warnings.filterwarnings("ignore", message="datetime.datetime.utcfromtimestamp\\(\\) is deprecated", module="pytz.tzinfo") BASE_DIR = os.path.dirname(os.path.abspath(__file__)) sys.path.append(os.path.abspath(BASE_DIR + "/..")) From 94d291c2fcaba6eb5c5a10796b040d74ffcf651d Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Thu, 17 Apr 2025 10:59:38 -0300 Subject: [PATCH 06/16] refactor: it's now spelled "datetime.UTC" --- ietf/api/__init__.py | 2 +- ietf/api/tests.py | 6 +++--- ietf/bin/aliases-from-json.py | 2 +- ietf/doc/models.py | 2 +- ietf/doc/templatetags/ballot_icon.py | 2 +- ietf/doc/tests_draft.py | 4 ++-- ietf/doc/tests_review.py | 2 +- ietf/doc/tests_utils.py | 2 +- ietf/doc/views_stats.py | 4 ++-- ietf/group/views.py | 2 +- ietf/idindex/index.py | 4 ++-- ietf/ietfauth/views.py | 2 +- ietf/ipr/mail.py | 4 ++-- ietf/ipr/views.py | 14 +++++++------- ietf/liaisons/tests.py | 8 ++++---- ietf/meeting/models.py | 6 +++--- ietf/meeting/tests_js.py | 2 +- ietf/meeting/tests_tasks.py | 2 +- ietf/meeting/tests_views.py | 22 +++++++++++----------- ietf/meeting/views.py | 6 +++--- ietf/nomcom/tests.py | 2 +- ietf/nomcom/views.py | 4 ++-- ietf/review/mailarch.py | 2 +- ietf/sync/iana.py | 8 ++++---- ietf/sync/tasks.py | 2 +- ietf/sync/tests.py | 6 +++--- ietf/utils/meetecho.py | 4 ++-- ietf/utils/serialize.py | 2 +- ietf/utils/tests_meetecho.py | 26 +++++++++++++------------- ietf/utils/timezone.py | 2 +- 30 files changed, 78 insertions(+), 78 deletions(-) diff --git a/ietf/api/__init__.py b/ietf/api/__init__.py index 9fadab8e6f..5e0a11980f 100644 --- a/ietf/api/__init__.py +++ b/ietf/api/__init__.py @@ -146,4 +146,4 @@ def dehydrate(self, bundle, for_list=True): class Serializer(tastypie.serializers.Serializer): def format_datetime(self, data): - return data.astimezone(datetime.timezone.utc).replace(tzinfo=None).isoformat(timespec="seconds") + "Z" + return data.astimezone(datetime.UTC).replace(tzinfo=None).isoformat(timespec="seconds") + "Z" diff --git a/ietf/api/tests.py b/ietf/api/tests.py index 809b45cc2b..aecb789b99 100644 --- a/ietf/api/tests.py +++ b/ietf/api/tests.py @@ -461,12 +461,12 @@ def test_api_add_session_attendees(self): self.assertTrue(session.attended_set.filter(person=recman).exists()) self.assertEqual( session.attended_set.get(person=recman).time, - datetime.datetime(2023, 9, 3, 12, 34, 56, tzinfo=datetime.timezone.utc), + datetime.datetime(2023, 9, 3, 12, 34, 56, tzinfo=datetime.UTC), ) self.assertTrue(session.attended_set.filter(person=otherperson).exists()) self.assertEqual( session.attended_set.get(person=otherperson).time, - datetime.datetime(2023, 9, 3, 3, 0, 19, tzinfo=datetime.timezone.utc), + datetime.datetime(2023, 9, 3, 3, 0, 19, tzinfo=datetime.UTC), ) def test_api_upload_polls_and_chatlog(self): @@ -1020,7 +1020,7 @@ def test_api_new_meeting_registration_v2_nomcom(self): self.assertEqual(volunteer.origin, 'registration') def test_api_version(self): - DumpInfo.objects.create(date=timezone.datetime(2022,8,31,7,10,1,tzinfo=datetime.timezone.utc), host='testapi.example.com',tz='UTC') + DumpInfo.objects.create(date=timezone.datetime(2022,8,31,7,10,1,tzinfo=datetime.UTC), host='testapi.example.com',tz='UTC') url = urlreverse('ietf.api.views.version') r = self.client.get(url) data = r.json() diff --git a/ietf/bin/aliases-from-json.py b/ietf/bin/aliases-from-json.py index a0c383a1ac..0da5d1f8b9 100644 --- a/ietf/bin/aliases-from-json.py +++ b/ietf/bin/aliases-from-json.py @@ -38,7 +38,7 @@ def generate_files(records, adest, vdest, postconfirm, vdomain): vpath = tmppath / "virtual" with apath.open("w") as afile, vpath.open("w") as vfile: - date = datetime.datetime.now(datetime.timezone.utc) + date = datetime.datetime.now(datetime.UTC) signature = f"# Generated by {Path(__file__).absolute()} at {date}\n" afile.write(signature) vfile.write(signature) diff --git a/ietf/doc/models.py b/ietf/doc/models.py index 55da70972c..8064b4525f 100644 --- a/ietf/doc/models.py +++ b/ietf/doc/models.py @@ -1157,7 +1157,7 @@ def fake_history_obj(self, rev): elif rev_events.exists(): time = rev_events.first().time else: - time = datetime.datetime.fromtimestamp(0, datetime.timezone.utc) + time = datetime.datetime.fromtimestamp(0, datetime.UTC) dh = DocHistory(name=self.name, rev=rev, doc=self, time=time, type=self.type, title=self.title, stream=self.stream, group=self.group) diff --git a/ietf/doc/templatetags/ballot_icon.py b/ietf/doc/templatetags/ballot_icon.py index a94c145007..07a6c7f926 100644 --- a/ietf/doc/templatetags/ballot_icon.py +++ b/ietf/doc/templatetags/ballot_icon.py @@ -196,7 +196,7 @@ def state_age_colored(doc): .time ) except IndexError: - state_datetime = datetime.datetime(1990, 1, 1, tzinfo=datetime.timezone.utc) + state_datetime = datetime.datetime(1990, 1, 1, tzinfo=datetime.UTC) days = (timezone.now() - state_datetime).days # loosely based on the Publish Path page at the iesg wiki if iesg_state == "lc": diff --git a/ietf/doc/tests_draft.py b/ietf/doc/tests_draft.py index 4753c4ff0c..fc234087c1 100644 --- a/ietf/doc/tests_draft.py +++ b/ietf/doc/tests_draft.py @@ -678,11 +678,11 @@ def test_in_draft_expire_freeze(self): datetime.datetime.combine( ietf_monday - datetime.timedelta(days=1), datetime.time(0, 0, 0), - tzinfo=datetime.timezone.utc, + tzinfo=datetime.UTC, ) )) self.assertFalse(in_draft_expire_freeze( - datetime.datetime.combine(ietf_monday, datetime.time(0, 0, 0), tzinfo=datetime.timezone.utc) + datetime.datetime.combine(ietf_monday, datetime.time(0, 0, 0), tzinfo=datetime.UTC) )) def test_warn_expirable_drafts(self): diff --git a/ietf/doc/tests_review.py b/ietf/doc/tests_review.py index 13ddbc22ba..d9e1cf228a 100644 --- a/ietf/doc/tests_review.py +++ b/ietf/doc/tests_review.py @@ -722,7 +722,7 @@ def test_search_mail_archive(self): messages = r.json()["messages"] self.assertEqual(len(messages), 2) - today = date_today(datetime.timezone.utc) + today = date_today(datetime.UTC) self.assertEqual(messages[0]["url"], "https://www.example.com/testmessage") self.assertTrue("John Doe" in messages[0]["content"]) diff --git a/ietf/doc/tests_utils.py b/ietf/doc/tests_utils.py index f610fe3d76..7db59819da 100644 --- a/ietf/doc/tests_utils.py +++ b/ietf/doc/tests_utils.py @@ -148,7 +148,7 @@ def test_update_action_holders_resets_age(self): doc = self.doc_in_iesg_state('pub-req') doc.action_holders.set([self.ad]) dah = doc.documentactionholder_set.get(person=self.ad) - dah.time_added = datetime.datetime(2020, 1, 1, tzinfo=datetime.timezone.utc) # arbitrary date in the past + dah.time_added = datetime.datetime(2020, 1, 1, tzinfo=datetime.UTC) # arbitrary date in the past dah.save() right_now = timezone.now() diff --git a/ietf/doc/views_stats.py b/ietf/doc/views_stats.py index 78e3a1ca52..028573b338 100644 --- a/ietf/doc/views_stats.py +++ b/ietf/doc/views_stats.py @@ -35,13 +35,13 @@ def model_to_timeline_data(model, field='time', **kwargs): assert field in [ f.name for f in model._meta.get_fields() ] objects = ( model.objects.filter(**kwargs) - .annotate(date=TruncDate(field, tzinfo=datetime.timezone.utc)) + .annotate(date=TruncDate(field, tzinfo=datetime.UTC)) .order_by('date') .values('date') .annotate(count=Count('id'))) if objects.exists(): obj_list = list(objects) - today = date_today(datetime.timezone.utc) + today = date_today(datetime.UTC) if not obj_list[-1]['date'] == today: obj_list += [ {'date': today, 'count': 0} ] data = [ ((e['date'].toordinal()-epochday)*1000*60*60*24, e['count']) for e in obj_list ] diff --git a/ietf/group/views.py b/ietf/group/views.py index f30569d230..1f8b9167da 100644 --- a/ietf/group/views.py +++ b/ietf/group/views.py @@ -881,7 +881,7 @@ def meetings(request, acronym, group_type=None): cutoff_date = revsub_dates_by_meeting[s.meeting.pk] else: cutoff_date = s.meeting.date + datetime.timedelta(days=s.meeting.submission_correction_day_offset) - s.cached_is_cutoff = date_today(datetime.timezone.utc) > cutoff_date + s.cached_is_cutoff = date_today(datetime.UTC) > cutoff_date future, in_progress, recent, past = group_sessions(sessions) diff --git a/ietf/idindex/index.py b/ietf/idindex/index.py index 4f021c0dc7..19eb29d4da 100644 --- a/ietf/idindex/index.py +++ b/ietf/idindex/index.py @@ -276,7 +276,7 @@ def active_drafts_index_by_group(extra_values=()): groups = [g for g in groups_dict.values() if hasattr(g, "active_drafts")] groups.sort(key=lambda g: g.acronym) - fallback_time = datetime.datetime(1950, 1, 1, tzinfo=datetime.timezone.utc) + fallback_time = datetime.datetime(1950, 1, 1, tzinfo=datetime.UTC) for g in groups: g.active_drafts.sort(key=lambda d: d.get("initial_rev_time", fallback_time)) @@ -302,6 +302,6 @@ def id_index_txt(with_abstracts=False): return render_to_string("idindex/id_index.txt", { 'groups': groups, - 'time': timezone.now().astimezone(datetime.timezone.utc).strftime("%Y-%m-%d %H:%M:%S %Z"), + 'time': timezone.now().astimezone(datetime.UTC).strftime("%Y-%m-%d %H:%M:%S %Z"), 'with_abstracts': with_abstracts, }) diff --git a/ietf/ietfauth/views.py b/ietf/ietfauth/views.py index 23f66ce824..75e21bf7bf 100644 --- a/ietf/ietfauth/views.py +++ b/ietf/ietfauth/views.py @@ -518,7 +518,7 @@ def confirm_password_reset(request, auth): password = data['password'] last_login = None if data['last_login']: - last_login = datetime.datetime.fromtimestamp(data['last_login'], datetime.timezone.utc) + last_login = datetime.datetime.fromtimestamp(data['last_login'], datetime.UTC) except django.core.signing.BadSignature: raise Http404("Invalid or expired auth") diff --git a/ietf/ipr/mail.py b/ietf/ipr/mail.py index 167b11956c..9bef751b95 100644 --- a/ietf/ipr/mail.py +++ b/ietf/ipr/mail.py @@ -66,9 +66,9 @@ def utc_from_string(s): if date is None: return None elif is_aware(date): - return date.astimezone(datetime.timezone.utc) + return date.astimezone(datetime.UTC) else: - return date.replace(tzinfo=datetime.timezone.utc) + return date.replace(tzinfo=datetime.UTC) # ---------------------------------------------------------------- # Email Functions diff --git a/ietf/ipr/views.py b/ietf/ipr/views.py index 24453df2d2..08979a3972 100644 --- a/ietf/ipr/views.py +++ b/ietf/ipr/views.py @@ -152,13 +152,13 @@ def ipr_rfc_number(disclosureDate, thirdPartyDisclosureFlag): # RFC publication date comes from the RFC Editor announcement ipr_rfc_pub_datetime = { - 1310 : datetime.datetime(1992, 3, 13, 0, 0, tzinfo=datetime.timezone.utc), - 1802 : datetime.datetime(1994, 3, 23, 0, 0, tzinfo=datetime.timezone.utc), - 2026 : datetime.datetime(1996, 10, 29, 0, 0, tzinfo=datetime.timezone.utc), - 3668 : datetime.datetime(2004, 2, 18, 0, 0, tzinfo=datetime.timezone.utc), - 3979 : datetime.datetime(2005, 3, 2, 2, 23, tzinfo=datetime.timezone.utc), - 4879 : datetime.datetime(2007, 4, 10, 18, 21, tzinfo=datetime.timezone.utc), - 8179 : datetime.datetime(2017, 5, 31, 23, 1, tzinfo=datetime.timezone.utc), + 1310 : datetime.datetime(1992, 3, 13, 0, 0, tzinfo=datetime.UTC), + 1802 : datetime.datetime(1994, 3, 23, 0, 0, tzinfo=datetime.UTC), + 2026 : datetime.datetime(1996, 10, 29, 0, 0, tzinfo=datetime.UTC), + 3668 : datetime.datetime(2004, 2, 18, 0, 0, tzinfo=datetime.UTC), + 3979 : datetime.datetime(2005, 3, 2, 2, 23, tzinfo=datetime.UTC), + 4879 : datetime.datetime(2007, 4, 10, 18, 21, tzinfo=datetime.UTC), + 8179 : datetime.datetime(2017, 5, 31, 23, 1, tzinfo=datetime.UTC), } if disclosureDate < ipr_rfc_pub_datetime[1310]: diff --git a/ietf/liaisons/tests.py b/ietf/liaisons/tests.py index 1742687f14..98619207c8 100644 --- a/ietf/liaisons/tests.py +++ b/ietf/liaisons/tests.py @@ -715,7 +715,7 @@ def test_add_incoming_liaison(self): from_groups = [ str(g.pk) for g in Group.objects.filter(type="sdo") ] to_group = Group.objects.get(acronym="mars") submitter = Person.objects.get(user__username="marschairman") - today = date_today(datetime.timezone.utc) + today = date_today(datetime.UTC) related_liaison = liaison r = self.client.post(url, dict(from_groups=from_groups, @@ -800,7 +800,7 @@ def test_add_outgoing_liaison(self): from_group = Group.objects.get(acronym="mars") to_group = Group.objects.filter(type="sdo")[0] submitter = Person.objects.get(user__username="marschairman") - today = date_today(datetime.timezone.utc) + today = date_today(datetime.UTC) related_liaison = liaison r = self.client.post(url, dict(from_groups=str(from_group.pk), @@ -870,7 +870,7 @@ def test_add_outgoing_liaison_unapproved_post_only(self): from_group = Group.objects.get(acronym="mars") to_group = Group.objects.filter(type="sdo")[0] submitter = Person.objects.get(user__username="marschairman") - today = date_today(datetime.timezone.utc) + today = date_today(datetime.UTC) r = self.client.post(url, dict(from_groups=str(from_group.pk), from_contact=submitter.email_address(), @@ -1054,7 +1054,7 @@ def test_search(self): LiaisonStatementEventFactory(type_id='posted', statement__body="Has recently in its body",statement__from_groups=[GroupFactory(type_id='sdo',acronym='ulm'),]) # Statement 2 s2 = LiaisonStatementEventFactory(type_id='posted', statement__body="That word does not occur here", statement__title="Nor does it occur here") - s2.time=datetime.datetime(2010, 1, 1, tzinfo=datetime.timezone.utc) + s2.time=datetime.datetime(2010, 1, 1, tzinfo=datetime.UTC) s2.save() # test list only, no search filters diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py index 003f8cd76e..14f79d5b9e 100644 --- a/ietf/meeting/models.py +++ b/ietf/meeting/models.py @@ -145,7 +145,7 @@ def get_00_cutoff(self): cutoff_date = importantdate.date else: cutoff_date = self.date + datetime.timedelta(days=ImportantDateName.objects.get(slug='idcutoff').default_offset_days) - cutoff_time = datetime_from_date(cutoff_date, datetime.timezone.utc) + self.idsubmit_cutoff_time_utc + cutoff_time = datetime_from_date(cutoff_date, datetime.UTC) + self.idsubmit_cutoff_time_utc return cutoff_time def get_01_cutoff(self): @@ -157,7 +157,7 @@ def get_01_cutoff(self): cutoff_date = importantdate.date else: cutoff_date = self.date + datetime.timedelta(days=ImportantDateName.objects.get(slug='idcutoff').default_offset_days) - cutoff_time = datetime_from_date(cutoff_date, datetime.timezone.utc) + self.idsubmit_cutoff_time_utc + cutoff_time = datetime_from_date(cutoff_date, datetime.UTC) + self.idsubmit_cutoff_time_utc return cutoff_time def get_reopen_time(self): @@ -1168,7 +1168,7 @@ def can_manage_materials(self, user): return can_manage_materials(user,self.group) def is_material_submission_cutoff(self): - return date_today(datetime.timezone.utc) > self.meeting.get_submission_correction_date() + return date_today(datetime.UTC) > self.meeting.get_submission_correction_date() def joint_with_groups_acronyms(self): return [group.acronym for group in self.joint_with_groups.all()] diff --git a/ietf/meeting/tests_js.py b/ietf/meeting/tests_js.py index a184a7c6d0..262b47652c 100644 --- a/ietf/meeting/tests_js.py +++ b/ietf/meeting/tests_js.py @@ -1576,7 +1576,7 @@ def test_delete_timeslot_cancel(self): def do_delete_time_interval_test(self, cancel=False): delete_time_local = datetime_from_date(self.meeting.date, self.meeting.tz()).replace(hour=10) - delete_time = delete_time_local.astimezone(datetime.timezone.utc) + delete_time = delete_time_local.astimezone(datetime.UTC) duration = datetime.timedelta(minutes=60) delete: [TimeSlot] = TimeSlotFactory.create_batch( # type: ignore[annotation-unchecked] diff --git a/ietf/meeting/tests_tasks.py b/ietf/meeting/tests_tasks.py index c026a99835..aed2eeb117 100644 --- a/ietf/meeting/tests_tasks.py +++ b/ietf/meeting/tests_tasks.py @@ -21,7 +21,7 @@ def test_proceedings_content_refresh_task(self, mock_generate): meeting127 = MeetingFactory(type_id="ietf", number="127") # 24 * 5 + 7 # Times to be returned - now_utc = datetime.datetime.now(tz=datetime.timezone.utc) + now_utc = datetime.datetime.now(tz=datetime.UTC) hour_00_utc = now_utc.replace(hour=0) hour_01_utc = now_utc.replace(hour=1) hour_07_utc = now_utc.replace(hour=7) diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index dfb414b61b..2eec586a00 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -321,11 +321,11 @@ def test_meeting_agenda(self): self.assertContains(r, session.group.parent.acronym.upper()) self.assertContains(r, slot.location.name) self.assertContains(r, "{}-{}".format( - slot.time.astimezone(datetime.timezone.utc).strftime("%H%M"), - (slot.time + slot.duration).astimezone(datetime.timezone.utc).strftime("%H%M"), + slot.time.astimezone(datetime.UTC).strftime("%H%M"), + (slot.time + slot.duration).astimezone(datetime.UTC).strftime("%H%M"), )) self.assertContains(r, "shown in UTC") - updated = meeting.updated().astimezone(datetime.timezone.utc).strftime("%Y-%m-%d %H:%M:%S %Z") + updated = meeting.updated().astimezone(datetime.UTC).strftime("%Y-%m-%d %H:%M:%S %Z") self.assertContains(r, f"Updated {updated}") # text, invalid updated (none) @@ -369,8 +369,8 @@ def test_meeting_agenda(self): self.assertContains(r, session.group.parent.acronym.upper()) self.assertContains(r, slot.location.name) self.assertContains(r, registration_text) - start_time = slot.time.astimezone(datetime.timezone.utc) - end_time = slot.end_time().astimezone(datetime.timezone.utc) + start_time = slot.time.astimezone(datetime.UTC) + end_time = slot.end_time().astimezone(datetime.UTC) self.assertContains(r, '"{}","{}","{}"'.format( start_time.strftime("%Y-%m-%d"), start_time.strftime("%H%M"), @@ -1029,7 +1029,7 @@ def test_important_dates_ical(self): updated = meeting.updated() self.assertIsNotNone(updated) - expected_updated = updated.astimezone(datetime.timezone.utc).strftime("%Y%m%dT%H%M%SZ") + expected_updated = updated.astimezone(datetime.UTC).strftime("%Y%m%dT%H%M%SZ") self.assertContains(r, f"DTSTAMP:{expected_updated}") dtstamps_count = r.content.decode("utf-8").count(f"DTSTAMP:{expected_updated}") self.assertEqual(dtstamps_count, meeting.importantdate_set.count()) @@ -2105,8 +2105,8 @@ def test_editor_time_zone(self): # strftime() does not seem to support hours without leading 0, so do this manually time_label_string = f'{ts_start.hour:d}:{ts_start.minute:02d} - {ts_end.hour:d}:{ts_end.minute:02d}' self.assertIn(time_label_string, time_label.text()) - self.assertEqual(time_label.attr('data-start'), ts_start.astimezone(datetime.timezone.utc).isoformat()) - self.assertEqual(time_label.attr('data-end'), ts_end.astimezone(datetime.timezone.utc).isoformat()) + self.assertEqual(time_label.attr('data-start'), ts_start.astimezone(datetime.UTC).isoformat()) + self.assertEqual(time_label.attr('data-end'), ts_end.astimezone(datetime.UTC).isoformat()) ts_swap = time_label.find('.swap-timeslot-col') origin_label = ts_swap.attr('data-origin-label') @@ -2117,8 +2117,8 @@ def test_editor_time_zone(self): timeslot_elt = pq(f'#timeslot{timeslot.pk}') self.assertEqual(len(timeslot_elt), 1) - self.assertEqual(timeslot_elt.attr('data-start'), ts_start.astimezone(datetime.timezone.utc).isoformat()) - self.assertEqual(timeslot_elt.attr('data-end'), ts_end.astimezone(datetime.timezone.utc).isoformat()) + self.assertEqual(timeslot_elt.attr('data-start'), ts_start.astimezone(datetime.UTC).isoformat()) + self.assertEqual(timeslot_elt.attr('data-end'), ts_end.astimezone(datetime.UTC).isoformat()) timeslot_label = pq(f'#timeslot{timeslot.pk} .time-label') self.assertEqual(len(timeslot_label), 1) @@ -5221,7 +5221,7 @@ def test_upcoming_ical(self): updated = meeting.updated() self.assertIsNotNone(updated) - expected_updated = updated.astimezone(datetime.timezone.utc).strftime("%Y%m%dT%H%M%SZ") + expected_updated = updated.astimezone(datetime.UTC).strftime("%Y%m%dT%H%M%SZ") self.assertContains(r, f"DTSTAMP:{expected_updated}") # With default cached_updated, 1970-01-01 diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 591b934b58..96705b3ed3 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -146,7 +146,7 @@ def materials(request, num=None): begin_date = meeting.get_submission_start_date() cut_off_date = meeting.get_submission_cut_off_date() cor_cut_off_date = meeting.get_submission_correction_date() - today_utc = date_today(datetime.timezone.utc) + today_utc = date_today(datetime.UTC) old = timezone.now() - datetime.timedelta(days=1) if settings.SERVER_MODE != 'production' and '_testoverride' in request.GET: pass @@ -1914,7 +1914,7 @@ def slides_field(item): write_row(headings) - tz = datetime.timezone.utc if utc else schedule.meeting.tz() + tz = datetime.UTC if utc else schedule.meeting.tz() for item in filtered_assignments: row = [] row.append(item.timeslot.time.astimezone(tz).strftime("%Y-%m-%d")) @@ -2695,7 +2695,7 @@ def session_attendance(request, session_id, num): raise Http404("Bluesheets not found") cor_cut_off_date = session.meeting.get_submission_correction_date() - today_utc = date_today(datetime.timezone.utc) + today_utc = date_today(datetime.UTC) was_there = False can_add = False if request.user.is_authenticated: diff --git a/ietf/nomcom/tests.py b/ietf/nomcom/tests.py index 8f94cc7fc5..7e764c5a09 100644 --- a/ietf/nomcom/tests.py +++ b/ietf/nomcom/tests.py @@ -2927,7 +2927,7 @@ def test_decorate_volunteers_with_qualifications(self): elig_date.year - 3, elig_date.month, 28 if elig_date.month == 2 and elig_date.day == 29 else elig_date.day, - tzinfo=datetime.timezone.utc, + tzinfo=datetime.UTC, ) ) nomcom.volunteer_set.create(person=author_person) diff --git a/ietf/nomcom/views.py b/ietf/nomcom/views.py index d34126b1e7..c6cce14a79 100644 --- a/ietf/nomcom/views.py +++ b/ietf/nomcom/views.py @@ -981,7 +981,7 @@ def view_feedback_topic(request, year, topic_id): reviewer = request.user.person last_seen = TopicFeedbackLastSeen.objects.filter(reviewer=reviewer,topic=topic).first() - last_seen_time = (last_seen and last_seen.time) or datetime.datetime(year=1, month=1, day=1, tzinfo=datetime.timezone.utc) + last_seen_time = (last_seen and last_seen.time) or datetime.datetime(year=1, month=1, day=1, tzinfo=datetime.UTC) if last_seen: last_seen.save() else: @@ -1041,7 +1041,7 @@ def view_feedback_nominee(request, year, nominee_id): }) last_seen = FeedbackLastSeen.objects.filter(reviewer=reviewer,nominee=nominee).first() - last_seen_time = (last_seen and last_seen.time) or datetime.datetime(year=1, month=1, day=1, tzinfo=datetime.timezone.utc) + last_seen_time = (last_seen and last_seen.time) or datetime.datetime(year=1, month=1, day=1, tzinfo=datetime.UTC) if last_seen: last_seen.save() else: diff --git a/ietf/review/mailarch.py b/ietf/review/mailarch.py index c34a6079ce..9c803cbf7d 100644 --- a/ietf/review/mailarch.py +++ b/ietf/review/mailarch.py @@ -91,7 +91,7 @@ def retrieve_messages_from_mbox(mbox_fileobj): utcdate = None d = email.utils.parsedate_tz(msg["Date"]) if d: - utcdate = datetime.datetime.fromtimestamp(email.utils.mktime_tz(d), datetime.timezone.utc) + utcdate = datetime.datetime.fromtimestamp(email.utils.mktime_tz(d), datetime.UTC) res.append({ "from": msg["From"], diff --git a/ietf/sync/iana.py b/ietf/sync/iana.py index f46fe407d4..0d40c5337e 100644 --- a/ietf/sync/iana.py +++ b/ietf/sync/iana.py @@ -66,8 +66,8 @@ def update_rfc_log_from_protocol_page(rfc_names, rfc_must_published_later_than): def fetch_changes_json(url, start, end): - url += "?start=%s&end=%s" % (urlquote(start.astimezone(datetime.timezone.utc).strftime("%Y-%m-%d %H:%M:%S")), - urlquote(end.astimezone(datetime.timezone.utc).strftime("%Y-%m-%d %H:%M:%S"))) + url += "?start=%s&end=%s" % (urlquote(start.astimezone(datetime.UTC).strftime("%Y-%m-%d %H:%M:%S")), + urlquote(end.astimezone(datetime.UTC).strftime("%Y-%m-%d %H:%M:%S"))) # HTTP basic auth username = "ietfsync" password = settings.IANA_SYNC_PASSWORD @@ -161,7 +161,7 @@ def update_history_with_changes(changes, send_email=True): for c in changes: docname = c['doc'] - timestamp = datetime.datetime.strptime(c["time"], "%Y-%m-%d %H:%M:%S",).replace(tzinfo=datetime.timezone.utc) + timestamp = datetime.datetime.strptime(c["time"], "%Y-%m-%d %H:%M:%S",).replace(tzinfo=datetime.UTC) if c['type'] in ("iana_state", "iana_review"): if c['type'] == "iana_state": @@ -247,7 +247,7 @@ def parse_review_email(text): review_time = parsedate_to_datetime(msg["Date"]) # parsedate_to_datetime() may return a naive timezone - treat as UTC if review_time.tzinfo is None or review_time.tzinfo.utcoffset(review_time) is None: - review_time = review_time.replace(tzinfo=datetime.timezone.utc) + review_time = review_time.replace(tzinfo=datetime.UTC) # by by = None diff --git a/ietf/sync/tasks.py b/ietf/sync/tasks.py index 53e23d7913..2411dbe7f1 100644 --- a/ietf/sync/tasks.py +++ b/ietf/sync/tasks.py @@ -151,7 +151,7 @@ def iana_protocols_update_task(): 2012, 11, 26, - tzinfo=datetime.timezone.utc, + tzinfo=datetime.UTC, ) try: diff --git a/ietf/sync/tests.py b/ietf/sync/tests.py index b0cdf863f0..d294d689d0 100644 --- a/ietf/sync/tests.py +++ b/ietf/sync/tests.py @@ -200,7 +200,7 @@ def test_iana_review_mail(self): doc_name, review_time, by, comment = iana.parse_review_email(msg.encode('utf-8')) self.assertEqual(doc_name, draft.name) - self.assertEqual(review_time, datetime.datetime(2012, 5, 10, 12, 0, rtime, tzinfo=datetime.timezone.utc)) + self.assertEqual(review_time, datetime.datetime(2012, 5, 10, 12, 0, rtime, tzinfo=datetime.UTC)) self.assertEqual(by, Person.objects.get(user__username="iana")) self.assertIn("there are no IANA Actions", comment.replace("\n", "")) @@ -234,7 +234,7 @@ def test_ingest_review_email(self, mock_parse_review_email, mock_add_review_comm args = ( "doc-name", - datetime.datetime.now(tz=datetime.timezone.utc), + datetime.datetime.now(tz=datetime.UTC), PersonFactory(), "yadda yadda yadda", ) @@ -1001,7 +1001,7 @@ def test_iana_protocols_update_task( ) self.assertEqual( published_later_than, - {datetime.datetime(2012,11,26,tzinfo=datetime.timezone.utc)} + {datetime.datetime(2012,11,26,tzinfo=datetime.UTC)} ) # try with an exception diff --git a/ietf/utils/meetecho.py b/ietf/utils/meetecho.py index 0dbf75736a..7654f67cd1 100644 --- a/ietf/utils/meetecho.py +++ b/ietf/utils/meetecho.py @@ -27,7 +27,7 @@ class MeetechoAPI: - timezone = datetime.timezone.utc + timezone = datetime.UTC def __init__( self, api_base: str, client_id: str, client_secret: str, request_timeout=3.01 @@ -504,7 +504,7 @@ def _should_send_update(self, session): if self.slides_notify_time < datetime.timedelta(0): return True # < 0 means "always" for a scheduled session else: - now = datetime.datetime.now(tz=datetime.timezone.utc) + now = datetime.datetime.now(tz=datetime.UTC) return (timeslot.time - self.slides_notify_time) < now < (timeslot.end_time() + self.slides_notify_time) def add(self, session: "Session", slides: "Document", order: int): diff --git a/ietf/utils/serialize.py b/ietf/utils/serialize.py index 342d211cf5..77f97942cb 100644 --- a/ietf/utils/serialize.py +++ b/ietf/utils/serialize.py @@ -16,7 +16,7 @@ def object_as_shallow_dict(obj): if isinstance(f, models.ManyToManyField): v = list(v.values_list("pk", flat=True)) elif isinstance(f, models.DateTimeField): - v = v.astimezone(datetime.timezone.utc).isoformat() + v = v.astimezone(datetime.UTC).isoformat() elif isinstance(f, models.DateField): v = v.strftime('%Y-%m-%d') diff --git a/ietf/utils/tests_meetecho.py b/ietf/utils/tests_meetecho.py index a10ac68c27..502e936483 100644 --- a/ietf/utils/tests_meetecho.py +++ b/ietf/utils/tests_meetecho.py @@ -98,7 +98,7 @@ def test_schedule_meeting(self): api_response = api.schedule_meeting( wg_token='my-token', room_id=18, - start_time=datetime.datetime(2021, 9, 14, 10, 0, 0, tzinfo=datetime.timezone.utc), + start_time=datetime.datetime(2021, 9, 14, 10, 0, 0, tzinfo=datetime.UTC), duration=datetime.timedelta(minutes=130), description='interim-2021-wgname-01', extrainfo='message for staff', @@ -127,7 +127,7 @@ def test_schedule_meeting(self): ) # same time in different time zones for start_time in [ - datetime.datetime(2021, 9, 14, 10, 0, 0, tzinfo=datetime.timezone.utc), + datetime.datetime(2021, 9, 14, 10, 0, 0, tzinfo=datetime.UTC), datetime.datetime(2021, 9, 14, 7, 0, 0, tzinfo=ZoneInfo('America/Halifax')), datetime.datetime(2021, 9, 14, 13, 0, 0, tzinfo=ZoneInfo('Europe/Kiev')), datetime.datetime(2021, 9, 14, 5, 0, 0, tzinfo=ZoneInfo('Pacific/Easter')), @@ -198,7 +198,7 @@ def test_fetch_meetings(self): '3d55bce0-535e-4ba8-bb8e-734911cf3c32': { 'room': { 'id': 18, - 'start_time': datetime.datetime(2021, 9, 14, 10, 0, 0, tzinfo=datetime.timezone.utc), + 'start_time': datetime.datetime(2021, 9, 14, 10, 0, 0, tzinfo=datetime.UTC), 'duration': datetime.timedelta(minutes=130), 'description': 'interim-2021-wgname-01', }, @@ -208,7 +208,7 @@ def test_fetch_meetings(self): 'e68e96d4-d38f-475b-9073-ecab46ca96a5': { 'room': { 'id': 23, - 'start_time': datetime.datetime(2021, 9, 15, 14, 30, 0, tzinfo=datetime.timezone.utc), + 'start_time': datetime.datetime(2021, 9, 15, 14, 30, 0, tzinfo=datetime.UTC), 'duration': datetime.timedelta(minutes=30), 'description': 'interim-2021-wgname-02', }, @@ -386,7 +386,7 @@ def test_request_helper_exception(self): def test_time_serialization(self): """Time de/serialization should be consistent""" - time = timezone.now().astimezone(datetime.timezone.utc).replace(microsecond=0) # cut off to 0 microseconds + time = timezone.now().astimezone(datetime.UTC).replace(microsecond=0) # cut off to 0 microseconds api = MeetechoAPI(API_BASE, CLIENT_ID, CLIENT_SECRET) self.assertEqual(api._deserialize_time(api._serialize_time(time)), time) @@ -400,7 +400,7 @@ def test_conference_from_api_dict(self): 'session-1-uuid': { 'room': { 'id': 1, - 'start_time': datetime.datetime(2022,2,4,1,2,3, tzinfo=datetime.timezone.utc), + 'start_time': datetime.datetime(2022,2,4,1,2,3, tzinfo=datetime.UTC), 'duration': datetime.timedelta(minutes=45), 'description': 'some-description', }, @@ -410,7 +410,7 @@ def test_conference_from_api_dict(self): 'session-2-uuid': { 'room': { 'id': 2, - 'start_time': datetime.datetime(2022,2,5,4,5,6, tzinfo=datetime.timezone.utc), + 'start_time': datetime.datetime(2022,2,5,4,5,6, tzinfo=datetime.UTC), 'duration': datetime.timedelta(minutes=90), 'description': 'another-description', }, @@ -427,7 +427,7 @@ def test_conference_from_api_dict(self): id=1, public_id='session-1-uuid', description='some-description', - start_time=datetime.datetime(2022, 2, 4, 1, 2, 3, tzinfo=datetime.timezone.utc), + start_time=datetime.datetime(2022, 2, 4, 1, 2, 3, tzinfo=datetime.UTC), duration=datetime.timedelta(minutes=45), url='https://example.com/some/url', deletion_token='delete-me', @@ -437,7 +437,7 @@ def test_conference_from_api_dict(self): id=2, public_id='session-2-uuid', description='another-description', - start_time=datetime.datetime(2022, 2, 5, 4, 5, 6, tzinfo=datetime.timezone.utc), + start_time=datetime.datetime(2022, 2, 5, 4, 5, 6, tzinfo=datetime.UTC), duration=datetime.timedelta(minutes=90), url='https://example.com/another/url', deletion_token='delete-me-too', @@ -453,7 +453,7 @@ def test_fetch(self, mock_fetch, _): 'session-1-uuid': { 'room': { 'id': 1, - 'start_time': datetime.datetime(2022,2,4,1,2,3, tzinfo=datetime.timezone.utc), + 'start_time': datetime.datetime(2022,2,4,1,2,3, tzinfo=datetime.UTC), 'duration': datetime.timedelta(minutes=45), 'description': 'some-description', }, @@ -472,7 +472,7 @@ def test_fetch(self, mock_fetch, _): id=1, public_id='session-1-uuid', description='some-description', - start_time=datetime.datetime(2022,2,4,1,2,3, tzinfo=datetime.timezone.utc), + start_time=datetime.datetime(2022,2,4,1,2,3, tzinfo=datetime.UTC), duration=datetime.timedelta(minutes=45), url='https://example.com/some/url', deletion_token='delete-me', @@ -488,7 +488,7 @@ def test_create(self, mock_schedule, _): 'session-1-uuid': { 'room': { 'id': 1, # value should match session_id param to cm.create() below - 'start_time': datetime.datetime(2022,2,4,1,2,3, tzinfo=datetime.timezone.utc), + 'start_time': datetime.datetime(2022,2,4,1,2,3, tzinfo=datetime.UTC), 'duration': datetime.timedelta(minutes=45), 'description': 'some-description', }, @@ -506,7 +506,7 @@ def test_create(self, mock_schedule, _): id=1, public_id='session-1-uuid', description='some-description', - start_time=datetime.datetime(2022,2,4,1,2,3, tzinfo=datetime.timezone.utc), + start_time=datetime.datetime(2022,2,4,1,2,3, tzinfo=datetime.UTC), duration=datetime.timedelta(minutes=45), url='https://example.com/some/url', deletion_token='delete-me', diff --git a/ietf/utils/timezone.py b/ietf/utils/timezone.py index a396b5e82d..e08dfa02f2 100644 --- a/ietf/utils/timezone.py +++ b/ietf/utils/timezone.py @@ -26,7 +26,7 @@ def _tzinfo(tz: Union[str, datetime.tzinfo, None]): Accepts a tzinfo or string containing a timezone name. Defaults to UTC if tz is None. """ if tz is None: - return datetime.timezone.utc + return datetime.UTC elif isinstance(tz, datetime.tzinfo): return tz else: From 17eb70387c5654e7b3831d5a9a2b3d41dc90a499 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Thu, 17 Apr 2025 11:15:54 -0300 Subject: [PATCH 07/16] feat: python 3.12 --- docker/base.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/base.Dockerfile b/docker/base.Dockerfile index f364456c7a..befee05e3b 100644 --- a/docker/base.Dockerfile +++ b/docker/base.Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.9-bookworm +FROM python:3.12-bookworm LABEL maintainer="IETF Tools Team " ENV DEBIAN_FRONTEND=noninteractive From b1466ab227a1f256821451de13cdf606c1620b50 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Thu, 17 Apr 2025 11:36:21 -0300 Subject: [PATCH 08/16] chore: suppress deprecation warning --- ietf/settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ietf/settings.py b/ietf/settings.py index a5e3315904..3542d06c6d 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -29,7 +29,8 @@ warnings.filterwarnings("ignore", message="datetime.datetime.utcnow\\(\\) is deprecated", module="botocore.auth") warnings.filterwarnings("ignore", message="currentThread\\(\\) is deprecated", module="coverage.pytracer") warnings.filterwarnings("ignore", message="datetime.datetime.utcfromtimestamp\\(\\) is deprecated", module="pytz.tzinfo") -warnings.filterwarnings("ignore", message="datetime.datetime.utcfromtimestamp\\(\\) is deprecated", module="pytz.tzinfo") +warnings.filterwarnings("ignore", message="datetime.datetime.utcfromtimestamp\\(\\) is deprecated", module="oic.utils.time_util") + BASE_DIR = os.path.dirname(os.path.abspath(__file__)) sys.path.append(os.path.abspath(BASE_DIR + "/..")) From 16daa76a604936bda75810a91d6914c342b65548 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Thu, 17 Apr 2025 11:36:54 -0300 Subject: [PATCH 09/16] fix: utcnow() -> now(datetime.UTC) --- ietf/iesg/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ietf/iesg/views.py b/ietf/iesg/views.py index df02754f2e..fa473dad27 100644 --- a/ietf/iesg/views.py +++ b/ietf/iesg/views.py @@ -101,7 +101,7 @@ def agenda_json(request, date=None): res = { "telechat-date": str(data["date"]), - "as-of": str(datetime.datetime.utcnow()), + "as-of": str(datetime.datetime.now(datetime.UTC)), "page-counts": telechat_page_count(date=get_agenda_date(date))._asdict(), "sections": {}, } From b43d2f8c8aef5268abfb94138511ccefcc7a0f47 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Thu, 17 Apr 2025 11:55:40 -0300 Subject: [PATCH 10/16] chore: suppress deprecation warning --- ietf/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ietf/settings.py b/ietf/settings.py index 3542d06c6d..3e91c60fdf 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -28,6 +28,7 @@ warnings.filterwarnings("ignore", message="HTTPResponse.getheader\\(\\) is deprecated", module='selenium.webdriver') warnings.filterwarnings("ignore", message="datetime.datetime.utcnow\\(\\) is deprecated", module="botocore.auth") warnings.filterwarnings("ignore", message="currentThread\\(\\) is deprecated", module="coverage.pytracer") +warnings.filterwarnings("ignore", message="co_lnotab is deprecated", module="coverage.parser") warnings.filterwarnings("ignore", message="datetime.datetime.utcfromtimestamp\\(\\) is deprecated", module="pytz.tzinfo") warnings.filterwarnings("ignore", message="datetime.datetime.utcfromtimestamp\\(\\) is deprecated", module="oic.utils.time_util") From 2cc94a0ca02c98d915eecc9d63bf02579831bcb0 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Thu, 17 Apr 2025 12:06:24 -0300 Subject: [PATCH 11/16] chore: more deprecation warnings --- ietf/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ietf/settings.py b/ietf/settings.py index 3e91c60fdf..6af330e65b 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -31,6 +31,7 @@ warnings.filterwarnings("ignore", message="co_lnotab is deprecated", module="coverage.parser") warnings.filterwarnings("ignore", message="datetime.datetime.utcfromtimestamp\\(\\) is deprecated", module="pytz.tzinfo") warnings.filterwarnings("ignore", message="datetime.datetime.utcfromtimestamp\\(\\) is deprecated", module="oic.utils.time_util") +warnings.filterwarnings("ignore", message="datetime.datetime.utcnow\\(\\) is deprecated", module="oic.utils.time_util") BASE_DIR = os.path.dirname(os.path.abspath(__file__)) From 3752410ebf39fbfd4620b48d7a655a608ad26f0f Mon Sep 17 00:00:00 2001 From: jennifer-richards <19472766+jennifer-richards@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:20:24 +0000 Subject: [PATCH 12/16] ci: update base image target version to 20250417T1507 --- dev/build/Dockerfile | 2 +- dev/build/TARGET_BASE | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/build/Dockerfile b/dev/build/Dockerfile index cd0a70667c..83c7884a43 100644 --- a/dev/build/Dockerfile +++ b/dev/build/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/ietf-tools/datatracker-app-base:20250402T1611 +FROM ghcr.io/ietf-tools/datatracker-app-base:20250417T1507 LABEL maintainer="IETF Tools Team " ENV DEBIAN_FRONTEND=noninteractive diff --git a/dev/build/TARGET_BASE b/dev/build/TARGET_BASE index 1195fc9a0b..97a575fef0 100644 --- a/dev/build/TARGET_BASE +++ b/dev/build/TARGET_BASE @@ -1 +1 @@ -20250402T1611 +20250417T1507 From ebbd341b25dea46fdedbe59121b6fffa23b73aa8 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Thu, 17 Apr 2025 17:41:49 -0300 Subject: [PATCH 13/16] chore: reorg / clean up deprecation ignore list Removed a few suppressions that were OBE based on running the tests and checking versions of the dependencies that were causing them. Reordered kwargs to make it more readable (to me anyway). --- ietf/settings.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/ietf/settings.py b/ietf/settings.py index 6af330e65b..25a84423f3 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -13,25 +13,26 @@ from hashlib import sha384 from typing import Any, Dict, List, Tuple # pyflakes:ignore +# DeprecationWarnings are suppressed by default, enable them warnings.simplefilter("always", DeprecationWarning) -warnings.filterwarnings("ignore", message="pkg_resources is deprecated as an API") -warnings.filterwarnings("ignore", "Log out via GET requests is deprecated") # happens in oidc_provider -warnings.filterwarnings("ignore", module="tastypie", message="The django.utils.datetime_safe module is deprecated.") -warnings.filterwarnings("ignore", module="oidc_provider", message="The django.utils.timezone.utc alias is deprecated.") + +# Warnings that must be resolved for Django 5.x +warnings.filterwarnings("ignore", "Log out via GET requests is deprecated") # caused by oidc_provider +warnings.filterwarnings("ignore", message="The django.utils.timezone.utc alias is deprecated.", module="oidc_provider") +warnings.filterwarnings("ignore", message="The django.utils.datetime_safe module is deprecated.", module="tastypie") warnings.filterwarnings("ignore", message="The USE_DEPRECATED_PYTZ setting,") # https://github.com/ietf-tools/datatracker/issues/5635 warnings.filterwarnings("ignore", message="The USE_L10N setting is deprecated.") # https://github.com/ietf-tools/datatracker/issues/5648 warnings.filterwarnings("ignore", message="django.contrib.auth.hashers.CryptPasswordHasher is deprecated.") # https://github.com/ietf-tools/datatracker/issues/5663 -warnings.filterwarnings("ignore", message="'urllib3\\[secure\\]' extra is deprecated") -warnings.filterwarnings("ignore", message="The logout\\(\\) view is superseded by") + +# Other DeprecationWarnings +warnings.filterwarnings("ignore", message="pkg_resources is deprecated as an API", module="pyang.plugin") warnings.filterwarnings("ignore", message="Report.file_reporters will no longer be available in Coverage.py 4.2", module="coverage.report") -warnings.filterwarnings("ignore", message="Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated", module="bleach") -warnings.filterwarnings("ignore", message="HTTPResponse.getheader\\(\\) is deprecated", module='selenium.webdriver') -warnings.filterwarnings("ignore", message="datetime.datetime.utcnow\\(\\) is deprecated", module="botocore.auth") warnings.filterwarnings("ignore", message="currentThread\\(\\) is deprecated", module="coverage.pytracer") warnings.filterwarnings("ignore", message="co_lnotab is deprecated", module="coverage.parser") -warnings.filterwarnings("ignore", message="datetime.datetime.utcfromtimestamp\\(\\) is deprecated", module="pytz.tzinfo") -warnings.filterwarnings("ignore", message="datetime.datetime.utcfromtimestamp\\(\\) is deprecated", module="oic.utils.time_util") +warnings.filterwarnings("ignore", message="datetime.datetime.utcnow\\(\\) is deprecated", module="botocore.auth") warnings.filterwarnings("ignore", message="datetime.datetime.utcnow\\(\\) is deprecated", module="oic.utils.time_util") +warnings.filterwarnings("ignore", message="datetime.datetime.utcfromtimestamp\\(\\) is deprecated", module="oic.utils.time_util") +warnings.filterwarnings("ignore", message="datetime.datetime.utcfromtimestamp\\(\\) is deprecated", module="pytz.tzinfo") BASE_DIR = os.path.dirname(os.path.abspath(__file__)) From 41bf5373e45204effb67a5c3ad4103d5aacaf40c Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Thu, 17 Apr 2025 21:10:34 -0300 Subject: [PATCH 14/16] chore: disable coverage test for now See the comment in settings.py for details. tl;dr coverage is unusably slow under python 3.12 as we're using it --- ietf/settings.py | 12 +++++++++--- ietf/utils/test_runner.py | 6 +++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/ietf/settings.py b/ietf/settings.py index 25a84423f3..55347e03c2 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -713,9 +713,15 @@ def skip_unreadable_post(record): TEST_COVERAGE_LATEST_FILE = os.path.join(BASE_DIR, "../latest-coverage.json") TEST_CODE_COVERAGE_CHECKER = None -if SERVER_MODE != 'production': - import coverage - TEST_CODE_COVERAGE_CHECKER = coverage.Coverage(source=[ BASE_DIR ], cover_pylib=False, omit=TEST_CODE_COVERAGE_EXCLUDE_FILES) +# TODO-PY312: figure out how to run coverage +# Context: the old version of coverage that we use (4.5.4, ca 2019) is falling back from its +# fast CTracer module to its very slow PyTracer when used on Python 3.12. It's not clear exactly +# why, but it's almost 3x slower. The situation may be better if we can update to a current +# version of coverage, but see https://github.com/nedbat/coveragepy/issues/1665 for more info. +# For now at least, disabling the checker completely. +# if SERVER_MODE != 'production': +# import coverage +# TEST_CODE_COVERAGE_CHECKER = coverage.Coverage(source=[ BASE_DIR ], cover_pylib=False, omit=TEST_CODE_COVERAGE_EXCLUDE_FILES) TEST_CODE_COVERAGE_REPORT_PATH = "coverage/" TEST_CODE_COVERAGE_REPORT_URL = os.path.join(STATIC_URL, TEST_CODE_COVERAGE_REPORT_PATH, "index.html") diff --git a/ietf/utils/test_runner.py b/ietf/utils/test_runner.py index c06e7876db..9536b2f5ea 100644 --- a/ietf/utils/test_runner.py +++ b/ietf/utils/test_runner.py @@ -462,7 +462,7 @@ def set_coverage_checking(flag=True): global template_coverage_collection global code_coverage_collection global url_coverage_collection - if settings.SERVER_MODE == 'test': + if settings.SERVER_MODE == 'test' and settings.TEST_CODE_COVERAGE_CHECKER is not None: if flag: settings.TEST_CODE_COVERAGE_CHECKER.collector.resume() template_coverage_collection = True @@ -589,7 +589,7 @@ def ignore_pattern(regex, pattern): self.skipTest("Coverage switched off with --skip-coverage") def code_coverage_test(self): - if self.runner.check_coverage: + if self.runner.check_coverage and self.runner.code_coverage_checker is not None: include = [ os.path.join(path, '*') for path in self.runner.test_paths ] checker = self.runner.code_coverage_checker checker.stop() @@ -830,7 +830,7 @@ def setup_test_environment(self, **kwargs): settings.MIDDLEWARE = ('ietf.utils.test_runner.record_urls_middleware',) + tuple(settings.MIDDLEWARE) self.code_coverage_checker = settings.TEST_CODE_COVERAGE_CHECKER - if not self.code_coverage_checker._started: + if self.code_coverage_checker and not self.code_coverage_checker._started: sys.stderr.write(" ** Warning: In %s: Expected the coverage checker to have\n" " been started already, but it wasn't. Doing so now. Coverage numbers\n" " will be off, though.\n" % __name__) From 0af6e1cd5184e07c98c60227a05492c80a32429f Mon Sep 17 00:00:00 2001 From: jennifer-richards <19472766+jennifer-richards@users.noreply.github.com> Date: Tue, 22 Apr 2025 15:11:03 +0000 Subject: [PATCH 15/16] ci: update base image target version to 20250422T1458 --- dev/build/Dockerfile | 2 +- dev/build/TARGET_BASE | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/build/Dockerfile b/dev/build/Dockerfile index 83c7884a43..f6050c9011 100644 --- a/dev/build/Dockerfile +++ b/dev/build/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/ietf-tools/datatracker-app-base:20250417T1507 +FROM ghcr.io/ietf-tools/datatracker-app-base:20250422T1458 LABEL maintainer="IETF Tools Team " ENV DEBIAN_FRONTEND=noninteractive diff --git a/dev/build/TARGET_BASE b/dev/build/TARGET_BASE index 97a575fef0..ad95b8fdaf 100644 --- a/dev/build/TARGET_BASE +++ b/dev/build/TARGET_BASE @@ -1 +1 @@ -20250417T1507 +20250422T1458 From c77d76a62d7ee72bf8b4c59fb961a9df67d8dd28 Mon Sep 17 00:00:00 2001 From: jennifer-richards <19472766+jennifer-richards@users.noreply.github.com> Date: Wed, 4 Jun 2025 20:25:06 +0000 Subject: [PATCH 16/16] ci: update base image target version to 20250604T2012 --- dev/build/Dockerfile | 2 +- dev/build/TARGET_BASE | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/build/Dockerfile b/dev/build/Dockerfile index f6050c9011..e74ef191cc 100644 --- a/dev/build/Dockerfile +++ b/dev/build/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/ietf-tools/datatracker-app-base:20250422T1458 +FROM ghcr.io/ietf-tools/datatracker-app-base:20250604T2012 LABEL maintainer="IETF Tools Team " ENV DEBIAN_FRONTEND=noninteractive diff --git a/dev/build/TARGET_BASE b/dev/build/TARGET_BASE index ad95b8fdaf..e195cb5c70 100644 --- a/dev/build/TARGET_BASE +++ b/dev/build/TARGET_BASE @@ -1 +1 @@ -20250422T1458 +20250604T2012