mirror of
https://github.com/certbot/certbot.git
synced 2026-07-27 00:00:44 +02:00
Add Python 3.10 support and tests (#9077)
Fixes https://github.com/certbot/certbot/issues/9058. The changes to the CI config are equivalent to the ones made in https://github.com/certbot/certbot/pull/8460. Other than ignoring some warnings raised by botocore, the main additional work that had to be done here was switching away from using `distutils.version.LooseVersion` since the entire `distutils` module was deprecated in Python 3.10. To do that, I took a few different approaches: * If the version strings being parsed are from Python packages such as Certbot or setuptools, I switched to using [pkg_resources.parse_version](https://setuptools.pypa.io/en/latest/pkg_resources.html#parsing-utilities) from `setuptools`. This functionality has been available since [setuptools 8.0 from 2014](https://setuptools.pypa.io/en/latest/history.html#id865). * If the version strings being parsed are not from Python packages, I added code equivalent to `distutils.version.LooseVersion` in `certbot.util.parse_loose_version`. * The code for `CERTBOT_PIP_NO_BINARY` can be completely removed since that variable isn't used or referenced anywhere in this repo. * add python 3.10 support * make some version changes * don't use looseversion in setup.py * switch to pkg_resources * deprecate get_strict_version * fix route53 tests * remove unused CERTBOT_PIP_NO_BINARY code * stop using distutils in letstest * add unit tests * more changelog entries
This commit is contained in:
@@ -6,11 +6,15 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
### Added
|
||||
|
||||
*
|
||||
* Support for Python 3.10 was added to Certbot and all of its components.
|
||||
* The function certbot.util.parse_loose_version was added to parse version
|
||||
strings in the same way as the now deprecated distutils.version.LooseVersion
|
||||
class from the Python standard library.
|
||||
|
||||
### Changed
|
||||
|
||||
*
|
||||
* The function certbot.util.get_strict_version was deprecated and will be
|
||||
removed in a future release.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
|
||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||
import parsedatetime
|
||||
import pkg_resources
|
||||
import pytz
|
||||
|
||||
import certbot
|
||||
@@ -33,7 +34,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
ALL_FOUR = ("cert", "privkey", "chain", "fullchain")
|
||||
README = "README"
|
||||
CURRENT_VERSION = util.get_strict_version(certbot.__version__)
|
||||
CURRENT_VERSION = pkg_resources.parse_version(certbot.__version__)
|
||||
BASE_PRIVKEY_MODE = 0o600
|
||||
|
||||
|
||||
@@ -457,7 +458,7 @@ class RenewableCert(interfaces.RenewableCert):
|
||||
|
||||
conf_version = self.configuration.get("version")
|
||||
if (conf_version is not None and
|
||||
util.get_strict_version(conf_version) > CURRENT_VERSION):
|
||||
pkg_resources.parse_version(conf_version) > CURRENT_VERSION):
|
||||
logger.info(
|
||||
"Attempting to parse the version %s renewal configuration "
|
||||
"file found at %s with version %s of Certbot. This might not "
|
||||
|
||||
+35
-6
@@ -1,10 +1,7 @@
|
||||
"""Utilities for all Certbot."""
|
||||
# distutils.version under virtualenv confuses pylint
|
||||
# For more info, see: https://github.com/PyCQA/pylint/issues/73
|
||||
import argparse
|
||||
import atexit
|
||||
import collections
|
||||
import distutils.version
|
||||
import errno
|
||||
import logging
|
||||
import platform
|
||||
@@ -14,6 +11,7 @@ import subprocess
|
||||
import sys
|
||||
from typing import Dict
|
||||
from typing import IO
|
||||
from typing import List
|
||||
from typing import Text
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
@@ -61,7 +59,7 @@ _INITIAL_PID = os.getpid()
|
||||
# program exits before the lock is cleaned up, it is automatically
|
||||
# released, but the file isn't deleted.
|
||||
_LOCKS: Dict[str, lock.LockFile] = {}
|
||||
|
||||
_VERSION_COMPONENT_RE = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE)
|
||||
|
||||
def env_no_snap_for_external_calls():
|
||||
"""
|
||||
@@ -612,8 +610,13 @@ def get_strict_version(normalized):
|
||||
:rtype: distutils.version.StrictVersion
|
||||
|
||||
"""
|
||||
# strict version ending with "a" and a number designates a pre-release
|
||||
return distutils.version.StrictVersion(normalized.replace(".dev", "a"))
|
||||
warnings.warn("certbot.util.get_strict_version is deprecated and will be "
|
||||
"removed in a future release.", DeprecationWarning)
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", DeprecationWarning)
|
||||
import distutils.version
|
||||
# strict version ending with "a" and a number designates a pre-release
|
||||
return distutils.version.StrictVersion(normalized.replace(".dev", "a"))
|
||||
|
||||
|
||||
def is_staging(srv):
|
||||
@@ -639,6 +642,32 @@ def atexit_register(func, *args, **kwargs):
|
||||
atexit.register(_atexit_call, func, *args, **kwargs)
|
||||
|
||||
|
||||
def parse_loose_version(version_string):
|
||||
"""Parses a version string into its components.
|
||||
|
||||
This code and the returned tuple is based on the now deprecated
|
||||
distutils.version.LooseVersion class from the Python standard library.
|
||||
Two LooseVersion classes and two lists as returned by this function should
|
||||
compare in the same way. See
|
||||
https://github.com/python/cpython/blob/v3.10.0/Lib/distutils/version.py#L205-L347.
|
||||
|
||||
:param str version_string: version string
|
||||
|
||||
:returns: list of parsed version string components
|
||||
:rtype: list
|
||||
|
||||
"""
|
||||
components: List[Union[int, str]]
|
||||
components = [x for x in _VERSION_COMPONENT_RE.split(version_string)
|
||||
if x and x != '.']
|
||||
for i, obj in enumerate(components):
|
||||
try:
|
||||
components[i] = int(obj)
|
||||
except ValueError:
|
||||
pass
|
||||
return components
|
||||
|
||||
|
||||
def _atexit_call(func, *args, **kwargs):
|
||||
if _INITIAL_PID == os.getpid():
|
||||
func(*args, **kwargs)
|
||||
|
||||
+3
-2
@@ -1,9 +1,9 @@
|
||||
import codecs
|
||||
from distutils.version import LooseVersion
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from pkg_resources import parse_version
|
||||
from setuptools import __version__ as setuptools_version
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
@@ -11,7 +11,7 @@ from setuptools import setup
|
||||
min_setuptools_version='39.0.1'
|
||||
# This conditional isn't necessary, but it provides better error messages to
|
||||
# people who try to install this package with older versions of setuptools.
|
||||
if LooseVersion(setuptools_version) < LooseVersion(min_setuptools_version):
|
||||
if parse_version(setuptools_version) < parse_version(min_setuptools_version):
|
||||
raise RuntimeError(f'setuptools {min_setuptools_version}+ is required')
|
||||
|
||||
# Workaround for https://bugs.python.org/issue8876, see
|
||||
@@ -132,6 +132,7 @@ setup(
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Security',
|
||||
'Topic :: System :: Installation/Setup',
|
||||
|
||||
@@ -592,6 +592,19 @@ class OsInfoTest(unittest.TestCase):
|
||||
self.assertEqual(cbutil.get_python_os_info(), ("testdist", "42"))
|
||||
|
||||
|
||||
class GetStrictVersionTest(unittest.TestCase):
|
||||
"""Test for certbot.util.get_strict_version."""
|
||||
|
||||
@classmethod
|
||||
def _call(cls, *args, **kwargs):
|
||||
from certbot.util import get_strict_version
|
||||
return get_strict_version(*args, **kwargs)
|
||||
|
||||
def test_it(self):
|
||||
with self.assertWarnsRegex(DeprecationWarning, "get_strict_version"):
|
||||
self._call("1.2.3")
|
||||
|
||||
|
||||
class AtexitRegisterTest(unittest.TestCase):
|
||||
"""Tests for certbot.util.atexit_register."""
|
||||
def setUp(self):
|
||||
@@ -624,5 +637,38 @@ class AtexitRegisterTest(unittest.TestCase):
|
||||
atexit_func(*args[1:], **kwargs)
|
||||
|
||||
|
||||
class ParseLooseVersionTest(unittest.TestCase):
|
||||
"""Test for certbot.util.parse_loose_version.
|
||||
|
||||
These tests are based on the original tests for
|
||||
distutils.version.LooseVersion at
|
||||
https://github.com/python/cpython/blob/v3.10.0/Lib/distutils/tests/test_version.py#L58-L81.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _call(cls, *args, **kwargs):
|
||||
from certbot.util import parse_loose_version
|
||||
return parse_loose_version(*args, **kwargs)
|
||||
|
||||
def test_less_than(self):
|
||||
comparisons = (('1.5.1', '1.5.2b2'),
|
||||
('3.4j', '1996.07.12'),
|
||||
('2g6', '11g'),
|
||||
('0.960923', '2.2beta29'),
|
||||
('1.13++', '5.5.kw'))
|
||||
for v1, v2 in comparisons:
|
||||
self.assertLess(self._call(v1), self._call(v2))
|
||||
|
||||
def test_equal(self):
|
||||
self.assertEqual(self._call('8.02'), self._call('8.02'))
|
||||
|
||||
def test_greater_than(self):
|
||||
comparisons = (('161', '3.10a'),
|
||||
('3.2.pl0', '3.1.1.6'))
|
||||
for v1, v2 in comparisons:
|
||||
self.assertGreater(self._call(v1), self._call(v2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
||||
Reference in New Issue
Block a user