Remove the dependency on pytz (#10350)

The `pytz` is obsoleted by Python 3.9.
This commit is contained in:
SATOH Fumiyasu 2025-07-29 00:00:16 +09:00 committed by GitHub
parent d8acf7cea0
commit 6ba8abe8d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 32 additions and 53 deletions

View file

@ -246,6 +246,7 @@ Authors
* [Sagi Kedmi](https://github.com/sagi)
* [Sam Lanning](https://github.com/samlanning)
* [sapics](https://github.com/sapics)
* [SATOH Fumiyasu](https://github.com/fumiyas)
* [Scott Barr](https://github.com/scottjbarr)
* [Scott Merrill](https://github.com/skpy)
* [Sebastian Bögl](https://github.com/TheBoegl)

View file

@ -10,7 +10,6 @@ install_requires = [
# relaxed to >=24.0.0 if needed.
'PyOpenSSL>=25.0.0',
'pyrfc3339',
'pytz>=2019.3',
'requests>=2.20.0',
]

View file

@ -6,7 +6,6 @@ import warnings
import josepy as jose
import pytest
import pytz
class FixedTest(unittest.TestCase):
@ -34,7 +33,7 @@ class RFC3339FieldTest(unittest.TestCase):
"""Tests for acme.fields.RFC3339Field."""
def setUp(self):
self.decoded = datetime.datetime(2015, 3, 27, tzinfo=pytz.UTC)
self.decoded = datetime.datetime(2015, 3, 27, tzinfo=datetime.timezone.utc)
self.encoded = '2015-03-27T00:00:00Z'
def test_default_encoder(self):

View file

@ -34,7 +34,7 @@ class RFC3339Field(jose.Field):
Handles decoding/encoding between RFC3339 strings and aware (not
naive) `datetime.datetime` objects
(e.g. ``datetime.datetime.now(pytz.UTC)``).
(e.g. ``datetime.datetime.now(datetime.timezone.utc)``).
"""

View file

@ -17,7 +17,6 @@ install_requires = [
# installation on Linux.
'pywin32>=300 ; sys_platform == "win32"',
'pyyaml',
'pytz>=2019.3',
# requests unvendored its dependencies in version 2.16.0 and this code relies on that for
# calling `urllib3.disable_warnings`.
'requests>=2.16.0',

View file

@ -5,7 +5,6 @@ to serve a mock OCSP responder during integration tests against Pebble.
"""
import datetime
import http.server as BaseHTTPServer
import pytz
import re
from typing import cast
from typing import Union
@ -55,7 +54,7 @@ class _ProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
else:
data = response.json()
now = datetime.datetime.now(pytz.UTC)
now = datetime.datetime.now(datetime.timezone.utc)
cert = x509.load_pem_x509_certificate(data['Certificate'].encode(), default_backend())
if data['Status'] != 'Revoked':
ocsp_status = ocsp.OCSPCertStatus.GOOD

View file

@ -12,6 +12,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
* Catches and ignores errors during the directory fetch for ARI checking so that these
errors do not hinder the actual certificate issuance
* Removed the dependency on `pytz`.
### Fixed

View file

@ -37,7 +37,6 @@ install_requires = [
'josepy>=2.0.0',
'parsedatetime>=2.4',
'pyrfc3339',
'pytz>=2019.3',
# This dependency needs to be added using environment markers to avoid its
# installation on Linux.
'pywin32>=300 ; sys_platform == "win32"',
@ -75,7 +74,6 @@ test_extras = [
'tox',
'types-httplib2',
'types-pyRFC3339',
'types-pytz',
'types-pywin32',
'types-requests',
'types-setuptools',

View file

@ -16,7 +16,6 @@ from typing import Optional
from cryptography.hazmat.primitives import serialization
import josepy as jose
import pyrfc3339
import pytz
from acme import fields as acme_fields
from acme import messages
@ -64,7 +63,7 @@ class Account:
self.regr = regr
self.meta = self.Meta(
# pyrfc3339 drops microseconds, make sure __eq__ is sane
creation_dt=datetime.datetime.now(tz=pytz.UTC).replace(microsecond=0),
creation_dt=datetime.datetime.now(tz=datetime.timezone.utc).replace(microsecond=0),
creation_host=socket.getfqdn(),
register_to_eff=None) if meta is None else meta

View file

@ -12,8 +12,6 @@ from typing import Tuple
from typing import TypeVar
from typing import Union
import pytz
from certbot import configuration
from certbot import crypto_util
from certbot import errors
@ -285,7 +283,7 @@ def human_readable_cert_info(config: configuration.NamespaceConfig, cert: storag
return None
if config.domains and not set(config.domains).issubset(cert.names()):
return None
now = datetime.datetime.now(pytz.UTC)
now = datetime.datetime.now(datetime.timezone.utc)
reasons = []
if cert.is_test_cert:

View file

@ -22,7 +22,6 @@ from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
from cryptography.hazmat.primitives.serialization import load_pem_private_key
import parsedatetime
import pytz
import certbot
from certbot import configuration
@ -104,7 +103,7 @@ def add_time_interval(base_time: datetime.datetime, interval: str,
interval += " days"
# try to use the same timezone, but fallback to UTC
tzinfo = base_time.tzinfo or pytz.UTC
tzinfo = base_time.tzinfo or datetime.timezone.utc
return textparser.parseDT(interval, base_time, tzinfo=tzinfo)[0]

View file

@ -6,7 +6,6 @@ from unittest import mock
import josepy as jose
import pytest
import pytz
from acme import messages
from certbot import errors
@ -27,7 +26,7 @@ class AccountTest(unittest.TestCase):
self.meta = Account.Meta(
creation_host="test.certbot.org",
creation_dt=datetime.datetime(
2015, 7, 4, 14, 4, 10, tzinfo=pytz.UTC))
2015, 7, 4, 14, 4, 10, tzinfo=datetime.timezone.utc))
self.acc = Account(self.regr, KEY, self.meta)
self.regr.__repr__ = mock.MagicMock(return_value="i_am_a_regr")
@ -111,7 +110,7 @@ class AccountFileStorageTest(test_util.ConfigTestCase):
meta = Account.Meta(
creation_host="test.example.org",
creation_dt=datetime.datetime(
2021, 1, 5, 14, 4, 10, tzinfo=pytz.UTC))
2021, 1, 5, 14, 4, 10, tzinfo=datetime.timezone.utc))
self.acc = Account(
regr=messages.RegistrationResource(
uri=None, body=messages.Registration()),

View file

@ -213,10 +213,8 @@ class CertificatesTest(BaseCertManagerTest):
mock_serial.return_value = 1234567890
import datetime
import pytz
from certbot._internal import cert_manager
expiry = datetime.datetime.now(pytz.UTC)
expiry = datetime.datetime.now(datetime.timezone.utc)
cert = mock.MagicMock(lineagename="nameone")
cert.target_expiry = expiry

View file

@ -6,7 +6,6 @@ from unittest import mock
import josepy
import pytest
import pytz
import requests
from acme import messages
@ -29,7 +28,7 @@ class SubscriptionTest(test_util.ConfigTestCase):
meta=account.Account.Meta(
creation_host='test.certbot.org',
creation_dt=datetime.datetime(
2015, 7, 4, 14, 4, 10, tzinfo=pytz.UTC)))
2015, 7, 4, 14, 4, 10, tzinfo=datetime.timezone.utc)))
self.config.email = 'certbot@example.org'
self.config.eff_email = None

View file

@ -19,7 +19,6 @@ import configobj
from cryptography import x509
import josepy as jose
import pytest
import pytz
from acme.messages import Error as acme_error
from certbot import errors
@ -423,7 +422,7 @@ class RevokeTest(test_util.TempDirTestCase):
self.meta = Account.Meta(
creation_host="test.certbot.org",
creation_dt=datetime.datetime(
2015, 7, 4, 14, 4, 10, tzinfo=pytz.UTC))
2015, 7, 4, 14, 4, 10, tzinfo=datetime.timezone.utc))
self.acc = Account(self.regr, JWK, self.meta)
self.mock_determine_account.return_value = (self.acc, None)

View file

@ -3,6 +3,7 @@
import contextlib
from datetime import datetime
from datetime import timedelta
from datetime import timezone
import sys
import unittest
from unittest import mock
@ -14,7 +15,6 @@ from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.x509 import ocsp as ocsp_lib
import pytest
import pytz
from certbot import errors
from certbot.tests import util as test_util
@ -65,7 +65,7 @@ class OCSPTestOpenSSL(unittest.TestCase):
@mock.patch('certbot.ocsp.crypto_util.notAfter')
@mock.patch('certbot.util.run_script')
def test_ocsp_revoked(self, mock_run, mock_na, mock_determine):
now = datetime.now(pytz.UTC)
now = datetime.now(timezone.utc)
cert_obj = mock.MagicMock()
cert_obj.cert_path = "x"
cert_obj.chain_path = "y"
@ -138,7 +138,7 @@ class OSCPTestCryptography(unittest.TestCase):
self.cert_obj = mock.MagicMock()
self.cert_obj.cert_path = self.cert_path
self.cert_obj.chain_path = self.chain_path
now = datetime.now(pytz.UTC)
now = datetime.now(timezone.utc)
self.mock_notAfter = mock.patch('certbot.ocsp.crypto_util.notAfter',
return_value=now + timedelta(hours=2))
self.mock_notAfter.start()
@ -324,8 +324,8 @@ def _construct_mock_ocsp_response(certificate_status, response_status):
responder_name=responder.subject,
certificates=[responder],
hash_algorithm=hashes.SHA1(),
next_update_utc=datetime.now(pytz.UTC) + timedelta(days=1),
this_update_utc=datetime.now(pytz.UTC) - timedelta(days=1),
next_update_utc=datetime.now(timezone.utc) + timedelta(days=1),
this_update_utc=datetime.now(timezone.utc) - timedelta(days=1),
signature_algorithm_oid=x509.oid.SignatureAlgorithmOID.RSA_WITH_SHA1,
)

View file

@ -7,7 +7,6 @@ import unittest
from unittest import mock
import pytest
import pytz
from acme import challenges
from certbot import configuration
@ -225,9 +224,9 @@ class RenewalTest(test_util.ConfigTestCase):
from certbot._internal import renewal
acme_client = mock.MagicMock()
mock_acme_from_config.return_value = acme_client
past = datetime.datetime(2025, 3, 19, 0, 0, 0, tzinfo=pytz.UTC)
now = datetime.datetime(2025, 4, 19, 0, 0, 0, tzinfo=pytz.UTC)
future = datetime.datetime(2025, 4, 19, 12, 0, 0, tzinfo=pytz.UTC)
past = datetime.datetime(2025, 3, 19, 0, 0, 0, tzinfo=datetime.timezone.utc)
now = datetime.datetime(2025, 4, 19, 0, 0, 0, tzinfo=datetime.timezone.utc)
future = datetime.datetime(2025, 4, 19, 12, 0, 0, tzinfo=datetime.timezone.utc)
mock_datetime.datetime.now.return_value = now
acme_client.renewal_time.return_value = past, future
@ -296,7 +295,7 @@ class RenewalTest(test_util.ConfigTestCase):
ari_server = "http://ari"
mock_acme = mock.MagicMock()
future = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=100000)
future = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=100000)
mock_acme.renewal_time.return_value = (future, future)
acme_clients = {}
acme_clients[ari_server] = mock_acme
@ -346,7 +345,7 @@ class RenewalTest(test_util.ConfigTestCase):
(1420070400, "10 weeks", True), (1420070400, "10 months", True),
(1420070400, "10 years", True), (1420070400, "99 months", True),
]:
sometime = datetime.datetime.fromtimestamp(current_time, pytz.UTC)
sometime = datetime.datetime.fromtimestamp(current_time, datetime.timezone.utc)
mock_datetime.datetime.now.return_value = sometime
mock_renewable_cert.configuration = {"renew_before_expiry": interval}
assert renewal.should_autorenew(self.config, mock_renewable_cert, acme_clients) == result, f"at {current_time}, with config '{interval}', ari response in future, expected {result}"
@ -384,7 +383,7 @@ class RenewalTest(test_util.ConfigTestCase):
(1420070400, "10 weeks", True), (1420070400, "10 months", True),
(1420070400, "10 years", True), (1420070400, "99 months", True),
]:
sometime = datetime.datetime.fromtimestamp(current_time, pytz.UTC)
sometime = datetime.datetime.fromtimestamp(current_time, datetime.timezone.utc)
mock_datetime.datetime.now.return_value = sometime
mock_renewable_cert.configuration = {"renew_before_expiry": interval}
mock_renewable_cert.server = ari_server
@ -398,7 +397,7 @@ class RenewalTest(test_util.ConfigTestCase):
mock_acme = mock.MagicMock()
ari_server = "http://ari"
future = datetime.datetime.now(pytz.UTC) + datetime.timedelta(seconds=1000)
future = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(seconds=1000)
mock_acme.renewal_time.return_value = (future, future)
acme_clients = {}
acme_clients[ari_server] = mock_acme

View file

@ -6,10 +6,10 @@ import stat
import sys
import unittest
from unittest import mock
import zoneinfo
import configobj
import pytest
import pytz
import certbot
from certbot import configuration
@ -19,7 +19,6 @@ from certbot.compat import filesystem
from certbot.compat import os
import certbot.tests.util as test_util
import datetime
from typing import Optional, Any
def unlink_all(rc_object):
@ -683,14 +682,13 @@ class RenewableCertTests(BaseRenewableCertTest):
from certbot._internal import storage
# this month has 30 days, and the next year is a leap year
time_1 = datetime.datetime(2003, 11, 20, 11, 59, 21, tzinfo=pytz.UTC)
time_1 = datetime.datetime(2003, 11, 20, 11, 59, 21, tzinfo=datetime.timezone.utc)
# this month has 31 days, and the next year is not a leap year
time_2 = datetime.datetime(2012, 10, 18, 21, 31, 16, tzinfo=pytz.UTC)
time_2 = datetime.datetime(2012, 10, 18, 21, 31, 16, tzinfo=datetime.timezone.utc)
# in different time zone (GMT+8)
time_3 = pytz.timezone('Asia/Shanghai').fromutc(
datetime.datetime(2015, 10, 26, 22, 25, 41))
time_3 = datetime.datetime(2015, 10, 26, 22, 25, 41, tzinfo=zoneinfo.ZoneInfo('Asia/Shanghai'))
intended = {
(time_1, ""): time_1,

View file

@ -1,6 +1,7 @@
"""Tools for checking certificate revocation."""
from datetime import datetime
from datetime import timedelta
from datetime import timezone
import logging
import re
import subprocess
@ -16,7 +17,6 @@ from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.x509 import ocsp
import pytz
import requests
from certbot import crypto_util
@ -83,7 +83,7 @@ class RevocationChecker:
# Let's Encrypt doesn't update OCSP for expired certificates,
# so don't check OCSP if the cert is expired.
# https://github.com/certbot/certbot/issues/7152
now = datetime.now(pytz.UTC)
now = datetime.now(timezone.utc)
if crypto_util.notAfter(cert_path) <= now:
return False
@ -239,7 +239,7 @@ def _check_ocsp_response(response_ocsp: 'ocsp.OCSPResponse', request_ocsp: 'ocsp
# See OpenSSL implementation as a reference:
# https://github.com/openssl/openssl/blob/ef45aa14c5af024fcb8bef1c9007f3d1c115bd85/crypto/ocsp/ocsp_cl.c#L338-L391
# thisUpdate/nextUpdate are expressed in UTC/GMT time zone
now = datetime.now(pytz.UTC)
now = datetime.now(timezone.utc)
if not response_ocsp.this_update_utc:
raise AssertionError('param thisUpdate is not set.')
if response_ocsp.this_update_utc > now + timedelta(minutes=5):

View file

@ -66,7 +66,6 @@ pytest==8.3.5 ; python_full_version >= "3.9.2" and python_version < "3.10"
python-augeas==0.5.0 ; python_full_version >= "3.9.2" and python_version < "3.10"
python-dateutil==2.9.0.post0 ; python_full_version >= "3.9.2" and python_version < "3.10"
python-digitalocean==1.11 ; python_full_version >= "3.9.2" and python_version < "3.10"
pytz==2019.3 ; python_full_version >= "3.9.2" and python_version < "3.10"
pywin32==310 ; python_full_version >= "3.9.2" and python_version < "3.10" and sys_platform == "win32"
pyyaml==6.0.2 ; python_full_version >= "3.9.2" and python_version < "3.10"
requests-file==2.1.0 ; python_full_version >= "3.9.2" and python_version < "3.10"
@ -83,7 +82,6 @@ tox==1.9.2 ; python_full_version >= "3.9.2" and python_version < "3.10"
types-httplib2==0.22.0.20250401 ; python_full_version >= "3.9.2" and python_version < "3.10"
types-pyrfc3339==2.0.1.20241107 ; python_full_version >= "3.9.2" and python_version < "3.10"
types-python-dateutil==2.9.0.20241206 ; python_full_version >= "3.9.2" and python_version < "3.10"
types-pytz==2025.2.0.20250326 ; python_full_version >= "3.9.2" and python_version < "3.10"
types-pywin32==310.0.0.20250429 ; python_full_version >= "3.9.2" and python_version < "3.10"
types-requests==2.31.0.6 ; python_full_version >= "3.9.2" and python_version < "3.10"
types-setuptools==80.0.0.20250429 ; python_full_version >= "3.9.2" and python_version < "3.10"

View file

@ -81,7 +81,6 @@ pycparser = "2.14"
pyparsing = "2.4.7"
python-augeas = "0.5.0"
python-digitalocean = "1.11"
pytz = "2019.3"
requests = "2.20.0"
six = "1.11.0"
urllib3 = "1.24.2"

View file

@ -136,7 +136,6 @@ pytest==8.4.1 ; python_full_version >= "3.9.2" and python_version < "4.0"
python-augeas==1.2.0 ; python_full_version >= "3.9.2" and python_version < "4.0"
python-dateutil==2.9.0.post0 ; python_full_version >= "3.9.2" and python_version < "4.0"
python-digitalocean==1.17.0 ; python_full_version >= "3.9.2" and python_version < "4.0"
pytz==2025.2 ; python_full_version >= "3.9.2" and python_version < "4.0"
pywin32-ctypes==0.2.3 ; python_full_version >= "3.9.2" and python_version < "4.0" and sys_platform == "win32"
pywin32==311 ; python_full_version >= "3.9.2" and python_version < "4.0" and sys_platform == "win32"
pyyaml==6.0.2 ; python_full_version >= "3.9.2" and python_version < "4.0"
@ -182,7 +181,6 @@ twine==6.1.0 ; python_full_version >= "3.9.2" and python_version < "4.0"
types-httplib2==0.22.0.20250622 ; python_full_version >= "3.9.2" and python_version < "4.0"
types-pyrfc3339==2.0.1.20241107 ; python_full_version >= "3.9.2" and python_version < "4.0"
types-python-dateutil==2.9.0.20250708 ; python_full_version >= "3.9.2" and python_version < "4.0"
types-pytz==2025.2.0.20250516 ; python_full_version >= "3.9.2" and python_version < "4.0"
types-pywin32==310.0.0.20250516 ; python_full_version >= "3.9.2" and python_version < "4.0"
types-requests==2.31.0.6 ; python_full_version >= "3.9.2" and python_version < "3.10"
types-requests==2.32.4.20250611 ; python_version >= "3.10" and python_version < "4.0"