diff --git a/acme/acme/_internal/tests/test_util.py b/acme/acme/_internal/tests/test_util.py index 9a5de4b25..2ba00d345 100644 --- a/acme/acme/_internal/tests/test_util.py +++ b/acme/acme/_internal/tests/test_util.py @@ -4,20 +4,25 @@ """ import os +import sys from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization import josepy as jose from josepy.util import ComparableECKey from OpenSSL import crypto -import pkg_resources + +if sys.version_info >= (3, 9): # pragma: no cover + import importlib.resources as importlib_resources +else: # pragma: no cover + import importlib_resources def load_vector(*names): """Load contents of a test vector.""" # luckily, resource_string opens file in binary mode - return pkg_resources.resource_string( - __name__, os.path.join('testdata', *names)) + vector_ref = importlib_resources.files(__package__).joinpath('testdata', *names) + return vector_ref.read_bytes() def _guess_loader(filename, loader_pem, loader_der): diff --git a/acme/setup.py b/acme/setup.py index 8581a1e23..0af48a645 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -22,6 +22,15 @@ docs_extras = [ ] test_extras = [ + # In theory we could scope importlib_resources to env marker 'python_version<"3.9"'. But this + # makes the pinning mechanism emit warnings when running `poetry lock` because in the corner + # case of an extra dependency with env marker coming from a setup.py file, it generate the + # invalid requirement 'importlib_resource>=1.3.1;python<=3.9;extra=="test"'. + # To fix the issue, we do not pass the env marker. This is fine because: + # - importlib_resources can be applied to any Python version, + # - this is a "test" extra dependency for limited audience, + # - it does not change anything at the end for the generated requirement files. + 'importlib_resources>=1.3.1', 'pytest', 'pytest-xdist', 'typing-extensions', diff --git a/certbot-apache/certbot_apache/_internal/apache_util.py b/certbot-apache/certbot_apache/_internal/apache_util.py index 54b9f9824..83bdf4f72 100644 --- a/certbot-apache/certbot_apache/_internal/apache_util.py +++ b/certbot-apache/certbot_apache/_internal/apache_util.py @@ -1,21 +1,28 @@ """ Utility functions for certbot-apache plugin """ +import atexit import binascii import fnmatch import logging import re import subprocess +import sys +from contextlib import ExitStack from typing import Dict from typing import Iterable from typing import List from typing import Optional from typing import Tuple -import pkg_resources - from certbot import errors from certbot import util from certbot.compat import os +if sys.version_info >= (3, 9): # pragma: no cover + import importlib.resources as importlib_resources +else: # pragma: no cover + import importlib_resources + + logger = logging.getLogger(__name__) @@ -248,6 +255,8 @@ def find_ssl_apache_conf(prefix: str) -> str: :return: the path the TLS Apache config file :rtype: str """ - return pkg_resources.resource_filename( - "certbot_apache", - os.path.join("_internal", "tls_configs", "{0}-options-ssl-apache.conf".format(prefix))) + file_manager = ExitStack() + atexit.register(file_manager.close) + ref = importlib_resources.files("certbot_apache").joinpath( + "_internal", "tls_configs", "{0}-options-ssl-apache.conf".format(prefix)) + return str(file_manager.enter_context(importlib_resources.as_file(ref))) diff --git a/certbot-apache/certbot_apache/_internal/constants.py b/certbot-apache/certbot_apache/_internal/constants.py index 0861e2da9..8e2aa3769 100644 --- a/certbot-apache/certbot_apache/_internal/constants.py +++ b/certbot-apache/certbot_apache/_internal/constants.py @@ -1,10 +1,14 @@ """Apache plugin constants.""" +import atexit +import sys +from contextlib import ExitStack from typing import Dict from typing import List -import pkg_resources - -from certbot.compat import os +if sys.version_info >= (3, 9): # pragma: no cover + import importlib.resources as importlib_resources +else: # pragma: no cover + import importlib_resources MOD_SSL_CONF_DEST = "options-ssl-apache.conf" """Name of the mod_ssl config file as saved @@ -37,8 +41,15 @@ ALL_SSL_OPTIONS_HASHES: List[str] = [ ] """SHA256 hashes of the contents of previous versions of all versions of MOD_SSL_CONF_SRC""" -AUGEAS_LENS_DIR = pkg_resources.resource_filename( - "certbot_apache", os.path.join("_internal", "augeas_lens")) +def _generate_augeas_lens_dir_static() -> str: + # This code ensures that the resource is accessible as file for the lifetime of current + # Python process, and will be automatically cleaned up on exit. + file_manager = ExitStack() + atexit.register(file_manager.close) + augeas_lens_dir_ref = importlib_resources.files("certbot_apache") / "_internal" / "augeas_lens" + return str(file_manager.enter_context(importlib_resources.as_file(augeas_lens_dir_ref))) + +AUGEAS_LENS_DIR = _generate_augeas_lens_dir_static() """Path to the Augeas lens directory""" REWRITE_HTTPS_ARGS: List[str] = [ diff --git a/certbot-apache/certbot_apache/_internal/tests/configurator_test.py b/certbot-apache/certbot_apache/_internal/tests/configurator_test.py index b625cc198..9f2328e46 100644 --- a/certbot-apache/certbot_apache/_internal/tests/configurator_test.py +++ b/certbot-apache/certbot_apache/_internal/tests/configurator_test.py @@ -5,7 +5,6 @@ import shutil import socket import sys import tempfile -import unittest from unittest import mock import pytest @@ -1659,20 +1658,23 @@ class InstallSslOptionsConfTest(util.ApacheTest): file has been manually edited by the user, and will refuse to update it. This test ensures that all necessary hashes are present. """ - import pkg_resources + if sys.version_info >= (3, 9): # pragma: no cover + import importlib.resources as importlib_resources + else: # pragma: no cover + import importlib_resources from certbot_apache._internal.constants import ALL_SSL_OPTIONS_HASHES - tls_configs_dir = pkg_resources.resource_filename( - "certbot_apache", os.path.join("_internal", "tls_configs")) - all_files = [os.path.join(tls_configs_dir, name) for name in os.listdir(tls_configs_dir) - if name.endswith('options-ssl-apache.conf')] - assert len(all_files) >= 1 - for one_file in all_files: - file_hash = crypto_util.sha256sum(one_file) - assert file_hash in ALL_SSL_OPTIONS_HASHES, \ - f"Constants.ALL_SSL_OPTIONS_HASHES must be appended with the sha256 " \ - f"hash of {one_file} when it is updated." + ref = importlib_resources.files("certbot_apache") / "_internal" / "tls_configs" + with importlib_resources.as_file(ref) as tls_configs_dir: + all_files = [os.path.join(tls_configs_dir, name) for name in os.listdir(tls_configs_dir) + if name.endswith('options-ssl-apache.conf')] + assert len(all_files) >= 1 + for one_file in all_files: + file_hash = crypto_util.sha256sum(one_file) + assert file_hash in ALL_SSL_OPTIONS_HASHES, \ + f"Constants.ALL_SSL_OPTIONS_HASHES must be appended with the sha256 " \ + f"hash of {one_file} when it is updated." def test_openssl_version(self): self.config._openssl_version = None diff --git a/certbot-apache/certbot_apache/_internal/tests/util.py b/certbot-apache/certbot_apache/_internal/tests/util.py index 57c374f07..5600e8be4 100644 --- a/certbot-apache/certbot_apache/_internal/tests/util.py +++ b/certbot-apache/certbot_apache/_internal/tests/util.py @@ -22,7 +22,7 @@ class ApacheTest(unittest.TestCase): # pylint: disable=arguments-differ self.temp_dir, self.config_dir, self.work_dir = common.dir_setup( test_dir=test_dir, - pkg=__name__) + pkg=__package__) self.config_path = os.path.join(self.temp_dir, config_root) self.vhost_path = os.path.join(self.temp_dir, vhost_root) diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 78ada824c..266693644 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -9,6 +9,7 @@ install_requires = [ # https://github.com/certbot/certbot/issues/8761 for more info. f'acme>={version}', f'certbot>={version}', + 'importlib_resources>=1.3.1; python_version < "3.9"', 'python-augeas', 'setuptools>=41.6.0', ] diff --git a/certbot-ci/certbot_integration_tests/nginx_tests/nginx_config.py b/certbot-ci/certbot_integration_tests/nginx_tests/nginx_config.py index 60f1696a8..583c00465 100644 --- a/certbot-ci/certbot_integration_tests/nginx_tests/nginx_config.py +++ b/certbot-ci/certbot_integration_tests/nginx_tests/nginx_config.py @@ -1,9 +1,15 @@ # -*- coding: utf-8 -*- """General purpose nginx test configuration generator.""" +import atexit import getpass +import sys +from contextlib import ExitStack from typing import Optional -import pkg_resources +if sys.version_info >= (3, 9): # pragma: no cover + import importlib.resources as importlib_resources +else: # pragma: no cover + import importlib_resources def construct_nginx_config(nginx_root: str, nginx_webroot: str, http_port: int, https_port: int, @@ -23,10 +29,18 @@ def construct_nginx_config(nginx_root: str, nginx_webroot: str, http_port: int, :return: a string containing the full nginx configuration :rtype: str """ - key_path = key_path if key_path \ - else pkg_resources.resource_filename('certbot_integration_tests', 'assets/key.pem') - cert_path = cert_path if cert_path \ - else pkg_resources.resource_filename('certbot_integration_tests', 'assets/cert.pem') + if not key_path: + file_manager = ExitStack() + atexit.register(file_manager.close) + ref = importlib_resources.files('certbot_integration_tests').joinpath('assets', 'key.pem') + key_path = str(file_manager.enter_context(importlib_resources.as_file(ref))) + + if not cert_path: + file_manager = ExitStack() + atexit.register(file_manager.close) + ref = importlib_resources.files('certbot_integration_tests').joinpath('assets', 'cert.pem') + cert_path = str(file_manager.enter_context(importlib_resources.as_file(ref))) + return '''\ # This error log will be written regardless of server scope error_log # definitions, so we have to set this here in the main scope. diff --git a/certbot-ci/certbot_integration_tests/rfc2136_tests/context.py b/certbot-ci/certbot_integration_tests/rfc2136_tests/context.py index 4a383fe56..d0f60adce 100644 --- a/certbot-ci/certbot_integration_tests/rfc2136_tests/context.py +++ b/certbot-ci/certbot_integration_tests/rfc2136_tests/context.py @@ -1,17 +1,21 @@ """Module to handle the context of RFC2136 integration tests.""" - from contextlib import contextmanager +import sys import tempfile from typing import Generator from typing import Iterable from typing import Tuple -from pkg_resources import resource_filename import pytest from certbot_integration_tests.certbot_tests import context as certbot_context from certbot_integration_tests.utils import certbot_call +if sys.version_info >= (3, 9): # pragma: no cover + import importlib.resources as importlib_resources +else: # pragma: no cover + import importlib_resources + class IntegrationTestsContext(certbot_context.IntegrationTestsContext): """Integration test context for certbot-dns-rfc2136""" @@ -44,15 +48,15 @@ class IntegrationTestsContext(certbot_context.IntegrationTestsContext): :yields: Path to credentials file :rtype: str """ - src_file = resource_filename('certbot_integration_tests', - 'assets/bind-config/rfc2136-credentials-{}.ini.tpl' - .format(label)) - - with open(src_file, 'r') as f: - contents = f.read().format( - server_address=self._dns_xdist['address'], - server_port=self._dns_xdist['port'] - ) + src_ref_file = importlib_resources.files('certbot_integration_tests').joinpath( + 'assets', 'bind-config', f'rfc2136-credentials-{label}.ini.tpl' + ) + with importlib_resources.as_file(src_ref_file) as src_file: + with open(src_file, 'r') as f: + contents = f.read().format( + server_address=self._dns_xdist['address'], + server_port=self._dns_xdist['port'] + ) with tempfile.NamedTemporaryFile('w+', prefix='rfc2136-creds-{}'.format(label), suffix='.ini', dir=self.workspace) as fp: diff --git a/certbot-ci/certbot_integration_tests/utils/certbot_call.py b/certbot-ci/certbot_integration_tests/utils/certbot_call.py index bcec38be2..d05578549 100755 --- a/certbot-ci/certbot_integration_tests/utils/certbot_call.py +++ b/certbot-ci/certbot_integration_tests/utils/certbot_call.py @@ -6,11 +6,8 @@ import subprocess import sys from typing import Dict from typing import List -from typing import Mapping from typing import Tuple -import pkg_resources - import certbot_integration_tests # pylint: disable=wildcard-import,unused-wildcard-import from certbot_integration_tests.utils.constants import * @@ -84,29 +81,14 @@ def _prepare_environ(workspace: str) -> Dict[str, str]: return new_environ -def _compute_additional_args(workspace: str, environ: Mapping[str, str], - force_renew: bool) -> List[str]: - additional_args = [] - output = subprocess.check_output(['certbot', '--version'], - universal_newlines=True, stderr=subprocess.STDOUT, - cwd=workspace, env=environ) - # Typical response is: output = 'certbot 0.31.0.dev0' - version_str = output.split(' ')[1].strip() - if pkg_resources.parse_version(version_str) >= pkg_resources.parse_version('0.30.0'): - additional_args.append('--no-random-sleep-on-renew') - - if force_renew: - additional_args.append('--renew-by-default') - - return additional_args - - def _prepare_args_env(certbot_args: List[str], directory_url: str, http_01_port: int, tls_alpn_01_port: int, config_dir: str, workspace: str, force_renew: bool) -> Tuple[List[str], Dict[str, str]]: new_environ = _prepare_environ(workspace) - additional_args = _compute_additional_args(workspace, new_environ, force_renew) + additional_args = ['--no-random-sleep-on-renew'] + if force_renew: + additional_args.append('--renew-by-default') command = [ 'certbot', diff --git a/certbot-ci/certbot_integration_tests/utils/dns_server.py b/certbot-ci/certbot_integration_tests/utils/dns_server.py index 8ec024bb3..2dabf5505 100644 --- a/certbot-ci/certbot_integration_tests/utils/dns_server.py +++ b/certbot-ci/certbot_integration_tests/utils/dns_server.py @@ -15,10 +15,13 @@ from typing import List from typing import Optional from typing import Type -from pkg_resources import resource_filename - from certbot_integration_tests.utils import constants +if sys.version_info >= (3, 9): # pragma: no cover + import importlib.resources as importlib_resources +else: # pragma: no cover + import importlib_resources + BIND_DOCKER_IMAGE = "internetsystemsconsortium/bind9:9.16" BIND_BIND_ADDRESS = ("127.0.0.1", 45953) @@ -80,13 +83,12 @@ class DNSServer: def _configure_bind(self) -> None: """Configure the BIND9 server based on the prebaked configuration""" - bind_conf_src = resource_filename( - "certbot_integration_tests", "assets/bind-config" - ) - for directory in ("conf", "zones"): - shutil.copytree( - os.path.join(bind_conf_src, directory), os.path.join(self.bind_root, directory) - ) + ref = importlib_resources.files("certbot_integration_tests") / "assets" / "bind-config" + with importlib_resources.as_file(ref) as path: + for directory in ("conf", "zones"): + shutil.copytree( + os.path.join(path, directory), os.path.join(self.bind_root, directory) + ) def _start_bind(self) -> None: """Launch the BIND9 server as a Docker container""" diff --git a/certbot-ci/certbot_integration_tests/utils/misc.py b/certbot-ci/certbot_integration_tests/utils/misc.py index 9db59d123..8260ccf5e 100644 --- a/certbot-ci/certbot_integration_tests/utils/misc.py +++ b/certbot-ci/certbot_integration_tests/utils/misc.py @@ -2,6 +2,7 @@ Misc module contains stateless functions that could be used during pytest execution, or outside during setup/teardown of the integration tests environment. """ +import atexit import contextlib import errno import functools @@ -30,13 +31,17 @@ from cryptography.hazmat.primitives.serialization import PrivateFormat from cryptography.x509 import Certificate from cryptography.x509 import load_pem_x509_certificate from OpenSSL import crypto -import pkg_resources import requests from certbot_integration_tests.certbot_tests.context import IntegrationTestsContext from certbot_integration_tests.utils.constants import PEBBLE_ALTERNATE_ROOTS from certbot_integration_tests.utils.constants import PEBBLE_MANAGEMENT_URL +if sys.version_info >= (3, 9): # pragma: no cover + import importlib.resources as importlib_resources +else: # pragma: no cover + import importlib_resources + RSA_KEY_TYPE = 'rsa' ECDSA_KEY_TYPE = 'ecdsa' @@ -125,7 +130,11 @@ def generate_test_file_hooks(config_dir: str, hook_probe: str) -> None: :param str config_dir: current certbot config directory :param str hook_probe: path to the hook probe to test hook scripts execution """ - hook_path = pkg_resources.resource_filename('certbot_integration_tests', 'assets/hook.py') + file_manager = contextlib.ExitStack() + atexit.register(file_manager.close) + hook_path_ref = importlib_resources.files('certbot_integration_tests').joinpath( + 'assets', 'hook.py') + hook_path = str(file_manager.enter_context(importlib_resources.as_file(hook_path_ref))) for hook_dir in list_renewal_hooks_dirs(config_dir): # We want an equivalent of bash `chmod -p $HOOK_DIR, that does not fail if one folder of @@ -260,9 +269,12 @@ def load_sample_data_path(workspace: str) -> str: :returns: the path to the loaded sample data directory :rtype: str """ - original = pkg_resources.resource_filename('certbot_integration_tests', 'assets/sample-config') - copied = os.path.join(workspace, 'sample-config') - shutil.copytree(original, copied, symlinks=True) + original_ref = importlib_resources.files('certbot_integration_tests').joinpath( + 'assets', 'sample-config' + ) + with importlib_resources.as_file(original_ref) as original: + copied = os.path.join(workspace, 'sample-config') + shutil.copytree(original, copied, symlinks=True) if os.name == 'nt': # Fix the symlinks on Windows if GIT is not configured to create them upon checkout diff --git a/certbot-ci/certbot_integration_tests/utils/pebble_artifacts.py b/certbot-ci/certbot_integration_tests/utils/pebble_artifacts.py index 9cd8ddbbe..8214eb761 100644 --- a/certbot-ci/certbot_integration_tests/utils/pebble_artifacts.py +++ b/certbot-ci/certbot_integration_tests/utils/pebble_artifacts.py @@ -1,33 +1,43 @@ # pylint: disable=missing-module-docstring - +import atexit import json import os import stat +import sys +from contextlib import ExitStack from typing import Tuple -import pkg_resources import requests from certbot_integration_tests.utils.constants import DEFAULT_HTTP_01_PORT from certbot_integration_tests.utils.constants import MOCK_OCSP_SERVER_PORT +if sys.version_info >= (3, 9): # pragma: no cover + import importlib.resources as importlib_resources +else: # pragma: no cover + import importlib_resources + PEBBLE_VERSION = 'v2.3.1' -ASSETS_PATH = pkg_resources.resource_filename('certbot_integration_tests', 'assets') def fetch(workspace: str, http_01_port: int = DEFAULT_HTTP_01_PORT) -> Tuple[str, str, str]: # pylint: disable=missing-function-docstring suffix = 'linux-amd64' if os.name != 'nt' else 'windows-amd64.exe' - pebble_path = _fetch_asset('pebble', suffix) - challtestsrv_path = _fetch_asset('pebble-challtestsrv', suffix) - pebble_config_path = _build_pebble_config(workspace, http_01_port) + file_manager = ExitStack() + atexit.register(file_manager.close) + pebble_path_ref = importlib_resources.files('certbot_integration_tests') / 'assets' + assets_path = str(file_manager.enter_context(importlib_resources.as_file(pebble_path_ref))) + + pebble_path = _fetch_asset('pebble', suffix, assets_path) + challtestsrv_path = _fetch_asset('pebble-challtestsrv', suffix, assets_path) + pebble_config_path = _build_pebble_config(workspace, http_01_port, assets_path) return pebble_path, challtestsrv_path, pebble_config_path -def _fetch_asset(asset: str, suffix: str) -> str: - asset_path = os.path.join(ASSETS_PATH, '{0}_{1}_{2}'.format(asset, PEBBLE_VERSION, suffix)) +def _fetch_asset(asset: str, suffix: str, assets_path: str) -> str: + asset_path = os.path.join(assets_path, '{0}_{1}_{2}'.format(asset, PEBBLE_VERSION, suffix)) if not os.path.exists(asset_path): asset_url = ('https://github.com/letsencrypt/pebble/releases/download/{0}/{1}_{2}' .format(PEBBLE_VERSION, asset, suffix)) @@ -40,15 +50,15 @@ def _fetch_asset(asset: str, suffix: str) -> str: return asset_path -def _build_pebble_config(workspace: str, http_01_port: int) -> str: +def _build_pebble_config(workspace: str, http_01_port: int, assets_path: str) -> str: config_path = os.path.join(workspace, 'pebble-config.json') with open(config_path, 'w') as file_h: file_h.write(json.dumps({ 'pebble': { 'listenAddress': '0.0.0.0:14000', 'managementListenAddress': '0.0.0.0:15000', - 'certificate': os.path.join(ASSETS_PATH, 'cert.pem'), - 'privateKey': os.path.join(ASSETS_PATH, 'key.pem'), + 'certificate': os.path.join(assets_path, 'cert.pem'), + 'privateKey': os.path.join(assets_path, 'key.pem'), 'httpPort': http_01_port, 'tlsPort': 5001, 'ocspResponderURL': 'http://127.0.0.1:{0}'.format(MOCK_OCSP_SERVER_PORT), diff --git a/certbot-ci/setup.py b/certbot-ci/setup.py index 0ea188e57..3fddcf104 100644 --- a/certbot-ci/setup.py +++ b/certbot-ci/setup.py @@ -1,20 +1,12 @@ -from pkg_resources import parse_version -from setuptools import __version__ as setuptools_version from setuptools import find_packages from setuptools import setup version = '0.32.0.dev0' -# setuptools 36.2+ is needed for support for environment markers -min_setuptools_version='36.2' -# 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 parse_version(setuptools_version) < parse_version(min_setuptools_version): - raise RuntimeError(f'setuptools {min_setuptools_version}+ is required') - install_requires = [ 'coverage', 'cryptography', + 'importlib_resources>=1.3.1; python_version < "3.9"', 'pyopenssl', 'pytest', 'pytest-cov', diff --git a/certbot-nginx/certbot_nginx/_internal/configurator.py b/certbot-nginx/certbot_nginx/_internal/configurator.py index ec2806ce3..ebba025e2 100644 --- a/certbot-nginx/certbot_nginx/_internal/configurator.py +++ b/certbot-nginx/certbot_nginx/_internal/configurator.py @@ -1,9 +1,12 @@ # pylint: disable=too-many-lines """Nginx Configuration""" +import atexit +from contextlib import ExitStack import logging import re import socket import subprocess +import sys import tempfile import time from typing import Any @@ -21,7 +24,6 @@ from typing import Type from typing import Union import OpenSSL -import pkg_resources from acme import challenges from acme import crypto_util as acme_crypto_util @@ -39,6 +41,11 @@ from certbot_nginx._internal import nginxparser from certbot_nginx._internal import obj from certbot_nginx._internal import parser +if sys.version_info >= (3, 9): # pragma: no cover + import importlib.resources as importlib_resources +else: # pragma: no cover + import importlib_resources + NAME_RANK = 0 START_WILDCARD_RANK = 1 END_WILDCARD_RANK = 2 @@ -163,8 +170,12 @@ class NginxConfigurator(common.Configurator): else: config_filename = "options-ssl-nginx-old.conf" - return pkg_resources.resource_filename( - "certbot_nginx", os.path.join("_internal", "tls_configs", config_filename)) + file_manager = ExitStack() + atexit.register(file_manager.close) + ref = importlib_resources.files("certbot_nginx").joinpath( + "_internal", "tls_configs", config_filename) + + return str(file_manager.enter_context(importlib_resources.as_file(ref))) @property def mod_ssl_conf(self) -> str: diff --git a/certbot-nginx/certbot_nginx/_internal/tests/configurator_test.py b/certbot-nginx/certbot_nginx/_internal/tests/configurator_test.py index 7f02f5b3f..24b21edca 100644 --- a/certbot-nginx/certbot_nginx/_internal/tests/configurator_test.py +++ b/certbot-nginx/certbot_nginx/_internal/tests/configurator_test.py @@ -1,6 +1,5 @@ """Test for certbot_nginx._internal.configurator.""" import sys -import unittest from unittest import mock import OpenSSL @@ -1075,22 +1074,21 @@ class InstallSslOptionsConfTest(util.NginxTest): file has been manually edited by the user, and will refuse to update it. This test ensures that all necessary hashes are present. """ - import pkg_resources - + if sys.version_info >= (3, 9): # pragma: no cover + import importlib.resources as importlib_resources + else: # pragma: no cover + import importlib_resources + from certbot_nginx._internal.constants import ALL_SSL_OPTIONS_HASHES - all_files = [ - pkg_resources.resource_filename("certbot_nginx", - os.path.join("_internal", "tls_configs", x)) - for x in ("options-ssl-nginx.conf", - "options-ssl-nginx-old.conf", - "options-ssl-nginx-tls12-only.conf") - ] - assert all_files - for one_file in all_files: - file_hash = crypto_util.sha256sum(one_file) - assert file_hash in ALL_SSL_OPTIONS_HASHES, \ - f"Constants.ALL_SSL_OPTIONS_HASHES must be appended with the sha256 " \ - f"hash of {one_file} when it is updated." + + tls_configs_ref = importlib_resources.files("certbot_nginx").joinpath( + "_internal", "tls_configs") + with importlib_resources.as_file(tls_configs_ref) as tls_configs_dir: + for tls_config_file in os.listdir(tls_configs_dir): + file_hash = crypto_util.sha256sum(os.path.join(tls_configs_dir, tls_config_file)) + assert file_hash in ALL_SSL_OPTIONS_HASHES, \ + f"Constants.ALL_SSL_OPTIONS_HASHES must be appended with the sha256 " \ + f"hash of {tls_config_file} when it is updated." def test_nginx_version_uses_correct_config(self): self.config.version = (1, 5, 8) diff --git a/certbot-nginx/certbot_nginx/_internal/tests/nginxparser_test.py b/certbot-nginx/certbot_nginx/_internal/tests/nginxparser_test.py index 51a454380..2873b43bd 100644 --- a/certbot-nginx/certbot_nginx/_internal/tests/nginxparser_test.py +++ b/certbot-nginx/certbot_nginx/_internal/tests/nginxparser_test.py @@ -70,50 +70,53 @@ class TestRawNginxParser(unittest.TestCase): ' image/jpeg jpg;}}}'.split('\n') def test_parse_from_file(self): - with open(util.get_data_filename('foo.conf')) as handle: - parsed = util.filter_comments(load(handle)) + with util.get_data_filename('foo.conf') as path: + with open(path) as handle: + parsed = util.filter_comments(load(handle)) assert parsed == \ [['user', 'www-data'], - [['http'], - [[['server'], [ - ['listen', '*:80', 'default_server', 'ssl'], - ['server_name', '*.www.foo.com', '*.www.example.com'], - ['root', '/home/ubuntu/sites/foo/'], - [['location', '/status'], [ - [['types'], [['image/jpeg', 'jpg']]], - ]], - [['location', '~', r'case_sensitive\.php$'], [ - ['index', 'index.php'], - ['root', '/var/root'], - ]], - [['location', '~*', r'case_insensitive\.php$'], []], - [['location', '=', r'exact_match\.php$'], []], - [['location', '^~', r'ignore_regex\.php$'], []] - ]]]]] + [['http'], + [[['server'], [ + ['listen', '*:80', 'default_server', 'ssl'], + ['server_name', '*.www.foo.com', '*.www.example.com'], + ['root', '/home/ubuntu/sites/foo/'], + [['location', '/status'], [ + [['types'], [['image/jpeg', 'jpg']]], + ]], + [['location', '~', r'case_sensitive\.php$'], [ + ['index', 'index.php'], + ['root', '/var/root'], + ]], + [['location', '~*', r'case_insensitive\.php$'], []], + [['location', '=', r'exact_match\.php$'], []], + [['location', '^~', r'ignore_regex\.php$'], []] + ]]]]] def test_parse_from_file2(self): - with open(util.get_data_filename('edge_cases.conf')) as handle: - parsed = util.filter_comments(load(handle)) + with util.get_data_filename('edge_cases.conf') as path: + with open(path) as handle: + parsed = util.filter_comments(load(handle)) assert parsed == \ [[['server'], [['server_name', 'simple']]], - [['server'], - [['server_name', 'with.if'], - [['location', '~', '^/services/.+$'], + [['server'], + [['server_name', 'with.if'], + [['location', '~', '^/services/.+$'], [[['if', '($request_filename', '~*', '\\.(ttf|woff)$)'], - [['add_header', 'Access-Control-Allow-Origin', '"*"']]]]]]], - [['server'], - [['server_name', 'with.complicated.headers'], - [['location', '~*', '\\.(?:gif|jpe?g|png)$'], + [['add_header', 'Access-Control-Allow-Origin', '"*"']]]]]]], + [['server'], + [['server_name', 'with.complicated.headers'], + [['location', '~*', '\\.(?:gif|jpe?g|png)$'], [['add_header', 'Pragma', 'public'], - ['add_header', - 'Cache-Control', '\'public, must-revalidate, proxy-revalidate\'', - '"test,;{}"', 'foo'], - ['blah', '"hello;world"'], - ['try_files', '$uri', '@rewrites']]]]]] + ['add_header', + 'Cache-Control', '\'public, must-revalidate, proxy-revalidate\'', + '"test,;{}"', 'foo'], + ['blah', '"hello;world"'], + ['try_files', '$uri', '@rewrites']]]]]] def test_parse_from_file3(self): - with open(util.get_data_filename('multiline_quotes.conf')) as handle: - parsed = util.filter_comments(load(handle)) + with util.get_data_filename('multiline_quotes.conf') as path: + with open(path) as handle: + parsed = util.filter_comments(load(handle)) assert parsed == \ [[['http'], [[['server'], @@ -129,13 +132,15 @@ class TestRawNginxParser(unittest.TestCase): ' end\'']]]]]]]] def test_abort_on_parse_failure(self): - with open(util.get_data_filename('broken.conf')) as handle: - with pytest.raises(ParseException): - load(handle) + with util.get_data_filename('broken.conf') as path: + with open(path) as handle: + with pytest.raises(ParseException): + load(handle) def test_dump_as_file(self): - with open(util.get_data_filename('nginx.conf')) as handle: - parsed = load(handle) + with util.get_data_filename('nginx.conf') as path: + with open(path) as handle: + parsed = load(handle) parsed[-1][-1].append(UnspacedList([['server'], [['listen', ' ', '443', ' ', 'ssl'], ['server_name', ' ', 'localhost'], @@ -155,8 +160,9 @@ class TestRawNginxParser(unittest.TestCase): assert parsed == parsed_new def test_comments(self): - with open(util.get_data_filename('minimalistic_comments.conf')) as handle: - parsed = load(handle) + with util.get_data_filename('minimalistic_comments.conf') as path: + with open(path) as handle: + parsed = load(handle) with tempfile.TemporaryFile(mode='w+t') as f: dump(parsed, f) diff --git a/certbot-nginx/certbot_nginx/_internal/tests/test_util.py b/certbot-nginx/certbot_nginx/_internal/tests/test_util.py index d38c515b4..4f95fe7b2 100644 --- a/certbot-nginx/certbot_nginx/_internal/tests/test_util.py +++ b/certbot-nginx/certbot_nginx/_internal/tests/test_util.py @@ -2,10 +2,11 @@ import copy import shutil import tempfile +import sys +from contextlib import contextmanager from unittest import mock import josepy as jose -import pkg_resources from certbot import util from certbot.compat import os @@ -14,6 +15,10 @@ from certbot.tests import util as test_util from certbot_nginx._internal import configurator from certbot_nginx._internal import nginxparser +if sys.version_info >= (3, 9): # pragma: no cover + import importlib.resources as importlib_resources +else: # pragma: no cover + import importlib_resources class NginxTest(test_util.ConfigTestCase): @@ -24,7 +29,7 @@ class NginxTest(test_util.ConfigTestCase): self.config = None self.temp_dir, self.config_dir, self.work_dir = common.dir_setup( - "etc_nginx", __name__) + "etc_nginx", __package__) self.logs_dir = tempfile.mkdtemp('logs') self.config_path = os.path.join(self.temp_dir, "etc_nginx") @@ -78,11 +83,12 @@ class NginxTest(test_util.ConfigTestCase): return config +@contextmanager def get_data_filename(filename): """Gets the filename of a test data file.""" - return pkg_resources.resource_filename( - __name__, os.path.join( - "testdata", "etc_nginx", filename)) + ref = importlib_resources.files(__package__) / "testdata" / "etc_nginx"/ filename + with importlib_resources.as_file(ref) as path: + yield path def filter_comments(tree): diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 6aa48c2fc..93995ca0d 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -9,6 +9,7 @@ install_requires = [ # https://github.com/certbot/certbot/issues/8761 for more info. f'acme>={version}', f'certbot>={version}', + 'importlib_resources>=1.3.1; python_version < "3.9"', # pyOpenSSL 23.1.0 is a bad release: https://github.com/pyca/pyopenssl/issues/1199 'PyOpenSSL>=17.5.0,!=23.1.0', 'pyparsing>=2.2.1', diff --git a/certbot/certbot/_internal/constants.py b/certbot/certbot/_internal/constants.py index 35ee5d54e..680be40fd 100644 --- a/certbot/certbot/_internal/constants.py +++ b/certbot/certbot/_internal/constants.py @@ -1,14 +1,20 @@ """Certbot constants.""" +import atexit import logging +import sys +from contextlib import ExitStack from typing import Any from typing import Dict -import pkg_resources - from acme import challenges from certbot.compat import misc from certbot.compat import os +if sys.version_info >= (3, 9): # pragma: no cover + import importlib.resources as importlib_resources +else: # pragma: no cover + import importlib_resources + SETUPTOOLS_PLUGINS_ENTRY_POINT = "certbot.plugins" """Setuptools entry point group name for plugins.""" @@ -220,8 +226,15 @@ SSL_DHPARAMS_DEST = "ssl-dhparams.pem" """Name of the ssl_dhparams file as saved in `certbot.configuration.NamespaceConfig.config_dir`.""" -SSL_DHPARAMS_SRC = pkg_resources.resource_filename( - "certbot", "ssl-dhparams.pem") +def _generate_ssl_dhparams_src_static() -> str: + # This code ensures that the resource is accessible as file for the lifetime of current + # Python process, and will be automatically cleaned up on exit. + file_manager = ExitStack() + atexit.register(file_manager.close) + ssl_dhparams_src_ref = importlib_resources.files("certbot") / "ssl-dhparams.pem" + return str(file_manager.enter_context(importlib_resources.as_file(ssl_dhparams_src_ref))) + +SSL_DHPARAMS_SRC = _generate_ssl_dhparams_src_static() """Path to the nginx ssl_dhparams file found in the Certbot distribution.""" UPDATED_SSL_DHPARAMS_DIGEST = ".updated-ssl-dhparams-pem-digest.txt" diff --git a/certbot/certbot/_internal/plugins/disco.py b/certbot/certbot/_internal/plugins/disco.py index 2ee07f083..c0426bcd8 100644 --- a/certbot/certbot/_internal/plugins/disco.py +++ b/certbot/certbot/_internal/plugins/disco.py @@ -1,5 +1,4 @@ """Utilities for plugins discovery and selection.""" -import itertools import logging import sys from typing import Callable @@ -13,8 +12,6 @@ from typing import Optional from typing import Type from typing import Union -import pkg_resources - from certbot import configuration from certbot import errors from certbot import interfaces @@ -22,6 +19,11 @@ from certbot._internal import constants from certbot.compat import os from certbot.errors import Error +if sys.version_info >= (3, 10): # pragma: no cover + import importlib.metadata as importlib_metadata +else: + import importlib_metadata + logger = logging.getLogger(__name__) @@ -35,7 +37,7 @@ class PluginEntryPoint: # this object is mutable, don't allow it to be hashed! __hash__ = None # type: ignore - def __init__(self, entry_point: pkg_resources.EntryPoint) -> None: + def __init__(self, entry_point: importlib_metadata.EntryPoint) -> None: self.name = self.entry_point_to_plugin_name(entry_point) self.plugin_cls: Type[interfaces.Plugin] = entry_point.load() self.entry_point = entry_point @@ -50,7 +52,7 @@ class PluginEntryPoint: return False @classmethod - def entry_point_to_plugin_name(cls, entry_point: pkg_resources.EntryPoint) -> str: + def entry_point_to_plugin_name(cls, entry_point: importlib_metadata.EntryPoint) -> str: """Unique plugin name for an ``entry_point``""" return entry_point.name @@ -75,7 +77,7 @@ class PluginEntryPoint: return getattr(self.plugin_cls, "hidden", False) def ifaces(self, *ifaces_groups: Iterable[Type]) -> bool: - """Does plugin implements specified interface groups?""" + """Does plugin implement specified interface groups?""" return not ifaces_groups or any( all(issubclass(self.plugin_cls, iface) for iface in ifaces) @@ -89,7 +91,6 @@ class PluginEntryPoint: def init(self, config: Optional[configuration.NamespaceConfig] = None) -> interfaces.Plugin: """Memoized plugin initialization.""" if not self._initialized: - self.entry_point.require() # fetch extras! # For plugins implementing ABCs Plugin, Authenticator or Installer, the following # line will raise an exception if some implementations of abstract methods are missing. self._initialized = self.plugin_cls(config, self.name) @@ -181,32 +182,31 @@ class PluginsRegistry(Mapping): plugin_paths = plugin_paths_string.split(':') if plugin_paths_string else [] # XXX should ensure this only happens once sys.path.extend(plugin_paths) - for plugin_path in plugin_paths: - pkg_resources.working_set.add_entry(plugin_path) - entry_points = itertools.chain( - pkg_resources.iter_entry_points( - constants.SETUPTOOLS_PLUGINS_ENTRY_POINT), - pkg_resources.iter_entry_points( - constants.OLD_SETUPTOOLS_PLUGINS_ENTRY_POINT),) - for entry_point in entry_points: + entry_points = list(importlib_metadata.entry_points( + group=constants.SETUPTOOLS_PLUGINS_ENTRY_POINT)) + old_entry_points = list(importlib_metadata.entry_points( + group=constants.OLD_SETUPTOOLS_PLUGINS_ENTRY_POINT)) + for entry_point in entry_points + old_entry_points: try: cls._load_entry_point(entry_point, plugins) except Exception as e: raise errors.PluginError( - f"The '{entry_point.module_name}' plugin errored while loading: {e}. " - "You may need to remove or update this plugin. The Certbot log will " - "contain the full error details and this should be reported to the " - "plugin developer.") from e + f"The '{entry_point.module}' plugin errored while loading: {e}. " + "You may need to remove or update this plugin. The Certbot log will " + "contain the full error details and this should be reported to the " + "plugin developer.") from e return cls(plugins) @classmethod - def _load_entry_point(cls, entry_point: pkg_resources.EntryPoint, + def _load_entry_point(cls, entry_point: importlib_metadata.EntryPoint, plugins: Dict[str, PluginEntryPoint]) -> None: plugin_ep = PluginEntryPoint(entry_point) if plugin_ep.name in plugins: other_ep = plugins[plugin_ep.name] - plugin1 = plugin_ep.entry_point.dist.key if plugin_ep.entry_point.dist else "unknown" - plugin2 = other_ep.entry_point.dist.key if other_ep.entry_point.dist else "unknown" + plugin1_dist = plugin_ep.entry_point.dist + plugin2_dist = other_ep.entry_point.dist + plugin1 = plugin1_dist.name.lower() if plugin1_dist else "unknown" + plugin2 = plugin2_dist.name.lower() if plugin2_dist else "unknown" raise Exception("Duplicate plugin name {0} from {1} and {2}.".format( plugin_ep.name, plugin1, plugin2)) if issubclass(plugin_ep.plugin_cls, interfaces.Plugin): diff --git a/certbot/certbot/_internal/storage.py b/certbot/certbot/_internal/storage.py index a535ac336..2388bbd95 100644 --- a/certbot/certbot/_internal/storage.py +++ b/certbot/certbot/_internal/storage.py @@ -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 pkg_resources import pytz import certbot @@ -38,12 +37,13 @@ from certbot._internal.plugins import disco as plugins_disco from certbot.compat import filesystem from certbot.compat import os from certbot.plugins import common as plugins_common +from certbot.util import parse_loose_version logger = logging.getLogger(__name__) ALL_FOUR = ("cert", "privkey", "chain", "fullchain") README = "README" -CURRENT_VERSION = pkg_resources.parse_version(certbot.__version__) +CURRENT_VERSION = parse_loose_version(certbot.__version__) BASE_PRIVKEY_MODE = 0o600 # pylint: disable=too-many-lines @@ -492,7 +492,7 @@ class RenewableCert(interfaces.RenewableCert): conf_version = self.configuration.get("version") if (conf_version is not None and - pkg_resources.parse_version(conf_version) > CURRENT_VERSION): + parse_loose_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 " diff --git a/certbot/certbot/_internal/tests/plugins/disco_test.py b/certbot/certbot/_internal/tests/plugins/disco_test.py index 97b3f2176..9b4903f8b 100644 --- a/certbot/certbot/_internal/tests/plugins/disco_test.py +++ b/certbot/certbot/_internal/tests/plugins/disco_test.py @@ -6,7 +6,6 @@ from typing import List import unittest from unittest import mock -import pkg_resources import pytest from certbot import errors @@ -15,30 +14,55 @@ from certbot._internal.plugins import null from certbot._internal.plugins import standalone from certbot._internal.plugins import webroot -EP_SA = pkg_resources.EntryPoint( - "sa", "certbot._internal.plugins.standalone", - attrs=("Authenticator",), - dist=mock.MagicMock(key="certbot")) -EP_WR = pkg_resources.EntryPoint( - "wr", "certbot._internal.plugins.webroot", - attrs=("Authenticator",), - dist=mock.MagicMock(key="certbot")) +if sys.version_info >= (3, 10): # pragma: no cover + import importlib.metadata as importlib_metadata +else: + import importlib_metadata + + +class _EntryPointLoadFail(importlib_metadata.EntryPoint): + def load(self): + raise RuntimeError("Loading failure") + + +EP_SA = importlib_metadata.EntryPoint( + name="sa", + value="certbot._internal.plugins.standalone:Authenticator", + group="certbot.plugins") + +EP_WR = importlib_metadata.EntryPoint( + name="wr", + value="certbot._internal.plugins.webroot:Authenticator", + group="certbot.plugins") + +EP_SA_LOADFAIL = _EntryPointLoadFail( + name="sa", + value="certbot._internal.plugins.standalone:Authenticator", + group="certbot.plugins") class PluginEntryPointTest(unittest.TestCase): """Tests for certbot._internal.plugins.disco.PluginEntryPoint.""" def setUp(self): - self.ep1 = pkg_resources.EntryPoint( - "ep1", "p1.ep1", dist=mock.MagicMock(key="p1")) - self.ep1prim = pkg_resources.EntryPoint( - "ep1", "p2.ep2", dist=mock.MagicMock(key="p2")) + self.ep1 = importlib_metadata.EntryPoint( + name="ep1", + value="p1.ep1:Authenticator", + group="certbot.plugins") + self.ep1prim = importlib_metadata.EntryPoint( + name="ep1", + value="p2.pe2:Authenticator", + group="certbot.plugins") # nested - self.ep2 = pkg_resources.EntryPoint( - "ep2", "p2.foo.ep2", dist=mock.MagicMock(key="p2")) + self.ep2 = importlib_metadata.EntryPoint( + name="ep2", + value="p2.foo.ep2:Authenticator", + group="certbot.plugins") # project name != top-level package name - self.ep3 = pkg_resources.EntryPoint( - "ep3", "a.ep3", dist=mock.MagicMock(key="p3")) + self.ep3 = importlib_metadata.EntryPoint( + name="ep3", + value="a.ep3:Authenticator", + group="certbot.plugins") from certbot._internal.plugins.disco import PluginEntryPoint self.plugin_ep = PluginEntryPoint(EP_SA) @@ -172,16 +196,18 @@ class PluginsRegistryTest(unittest.TestCase): self.plugin_ep.__hash__.side_effect = TypeError self.plugins = {self.plugin_ep.name: self.plugin_ep} self.reg = self._create_new_registry(self.plugins) - self.ep1 = pkg_resources.EntryPoint( - "ep1", "p1.ep1", dist=mock.MagicMock(key="p1")) + self.ep1 = importlib_metadata.EntryPoint( + name="ep1", + value="p1.ep1", + group="certbot.plugins") def test_find_all(self): from certbot._internal.plugins.disco import PluginsRegistry - with mock.patch("certbot._internal.plugins.disco.pkg_resources") as mock_pkg: - mock_pkg.iter_entry_points.side_effect = [ - iter([EP_SA]), iter([EP_WR, self.ep1]) + with mock.patch("certbot._internal.plugins.disco.importlib_metadata") as mock_meta: + mock_meta.entry_points.side_effect = [ + [EP_SA], [EP_WR, self.ep1], ] - with mock.patch.object(pkg_resources.EntryPoint, 'load') as mock_load: + with mock.patch.object(importlib_metadata.EntryPoint, 'load') as mock_load: mock_load.side_effect = [ standalone.Authenticator, webroot.Authenticator, null.Installer, null.Installer] @@ -196,10 +222,10 @@ class PluginsRegistryTest(unittest.TestCase): def test_find_all_error_message(self): from certbot._internal.plugins.disco import PluginsRegistry - with mock.patch("certbot._internal.plugins.disco.pkg_resources") as mock_pkg: - EP_SA.load = None # This triggers a TypeError when the entrypoint loads - mock_pkg.iter_entry_points.side_effect = [ - iter([EP_SA]), iter([EP_WR, self.ep1]) + with mock.patch("certbot._internal.plugins.disco.importlib_metadata") as mock_meta: + #EP_SA.load = None # This triggers a TypeError when the entrypoint loads + mock_meta.entry_points.side_effect = [ + [EP_SA_LOADFAIL], [EP_WR, self.ep1], ] with self.assertRaises(errors.PluginError) as cm: PluginsRegistry.find_all() diff --git a/certbot/certbot/plugins/common.py b/certbot/certbot/plugins/common.py index a6086acad..abc30bbdd 100644 --- a/certbot/certbot/plugins/common.py +++ b/certbot/certbot/plugins/common.py @@ -5,6 +5,7 @@ import argparse import logging import re import shutil +import sys import tempfile from typing import Any from typing import Callable @@ -16,8 +17,6 @@ from typing import Tuple from typing import Type from typing import TypeVar -import pkg_resources - from acme import challenges from certbot import achallenges from certbot import configuration @@ -32,6 +31,11 @@ from certbot.interfaces import Installer as AbstractInstaller from certbot.interfaces import Plugin as AbstractPlugin from certbot.plugins.storage import PluginStorage +if sys.version_info >= (3, 9): # pragma: no cover + import importlib.resources as importlib_resources +else: # pragma: no cover + import importlib_resources + logger = logging.getLogger(__name__) @@ -461,10 +465,9 @@ def dir_setup(test_dir: str, pkg: str) -> Tuple[str, str, str]: # pragma: no co filesystem.chmod(config_dir, constants.CONFIG_DIRS_MODE) filesystem.chmod(work_dir, constants.CONFIG_DIRS_MODE) - test_configs = pkg_resources.resource_filename( - pkg, os.path.join("testdata", test_dir)) - - shutil.copytree( - test_configs, os.path.join(temp_dir, test_dir), symlinks=True) + test_dir_ref = importlib_resources.files(pkg).joinpath("testdata", test_dir) + with importlib_resources.as_file(test_dir_ref) as path: + shutil.copytree( + path, os.path.join(temp_dir, test_dir), symlinks=True) return temp_dir, config_dir, work_dir diff --git a/certbot/certbot/tests/util.py b/certbot/certbot/tests/util.py index be8e24b55..bbe2ffe9c 100644 --- a/certbot/certbot/tests/util.py +++ b/certbot/certbot/tests/util.py @@ -1,4 +1,6 @@ """Test utilities.""" +import atexit +from contextlib import ExitStack from importlib import reload as reload_module import io import logging @@ -23,7 +25,6 @@ from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey import josepy as jose from OpenSSL import crypto -import pkg_resources from certbot import configuration from certbot import util @@ -36,6 +37,11 @@ from certbot.compat import os from certbot.display import util as display_util from certbot.plugins import common +if sys.version_info >= (3, 9): # pragma: no cover + import importlib.resources as importlib_resources +else: # pragma: no cover + import importlib_resources + class DummyInstaller(common.Installer): """Dummy installer plugin for test purpose.""" @@ -75,15 +81,17 @@ class DummyInstaller(common.Installer): def vector_path(*names: str) -> str: """Path to a test vector.""" - return pkg_resources.resource_filename( - __name__, os.path.join('testdata', *names)) + _file_manager = ExitStack() + atexit.register(_file_manager.close) + vector_ref = importlib_resources.files(__package__).joinpath('testdata', *names) + path = _file_manager.enter_context(importlib_resources.as_file(vector_ref)) + return str(path) def load_vector(*names: str) -> bytes: """Load contents of a test vector.""" - # luckily, resource_string opens file in binary mode - data = pkg_resources.resource_string( - __name__, os.path.join('testdata', *names)) + vector_ref = importlib_resources.files(__package__).joinpath('testdata', *names) + data = vector_ref.read_bytes() # Try at most to convert CRLF to LF when data is text try: return data.decode().replace('\r\n', '\n').encode() diff --git a/certbot/setup.py b/certbot/setup.py index 35924ca07..f607d558b 100644 --- a/certbot/setup.py +++ b/certbot/setup.py @@ -1,19 +1,10 @@ import codecs 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 -min_setuptools_version='41.6.0' -# 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 parse_version(setuptools_version) < parse_version(min_setuptools_version): - raise RuntimeError(f'setuptools {min_setuptools_version}+ is required') - def read_file(filename, encoding='utf8'): """Read unicode from given file.""" with codecs.open(filename, encoding=encoding) as fd: @@ -44,6 +35,8 @@ install_requires = [ 'configobj>=5.0.6', 'cryptography>=3.2.1', 'distro>=1.0.1', + 'importlib_resources>=1.3.1; python_version < "3.9"', + 'importlib_metadata>=4.6; python_version < "3.10"', 'josepy>=1.13.0', 'parsedatetime>=2.4', 'pyrfc3339', @@ -51,7 +44,7 @@ install_requires = [ # This dependency needs to be added using environment markers to avoid its # installation on Linux. 'pywin32>=300 ; sys_platform == "win32"', - f'setuptools>={min_setuptools_version}', + 'setuptools>=41.6.0', ] dev_extras = [ diff --git a/pytest.ini b/pytest.ini index 8d06655ff..46beca405 100644 --- a/pytest.ini +++ b/pytest.ini @@ -31,6 +31,8 @@ # 8) Ignore DeprecationWarning for datetime.utcfromtimestamp() triggered # when importing the pytz.tzinfo module # https://github.com/stub42/pytz/issues/105 +# 9) Boto3 is dropping support for Python 3.7 by end of 2023. Let's ignore the associated +# deprecation warning since we will also drop Python 3.7 soon. filterwarnings = error ignore:decodestring\(\) is a deprecated alias:DeprecationWarning:dns @@ -38,6 +40,7 @@ filterwarnings = ignore:'urllib3.contrib.pyopenssl:DeprecationWarning:requests_toolbelt ignore:update_symlinks is deprecated:PendingDeprecationWarning ignore:.*declare_namespace\(':DeprecationWarning - ignore:pkg_resources is deprecated as an API:DeprecationWarning:pkg_resources + ignore:pkg_resources is deprecated as an API:DeprecationWarning ignore:Python 3.7 support will be dropped:PendingDeprecationWarning ignore:datetime.utcfromtimestamp\(\) is deprecated:DeprecationWarning:pytz.tzinfo + ignore:Boto3 will no longer support Python 3.7 diff --git a/tools/oldest_constraints.txt b/tools/oldest_constraints.txt index fe3dcd52d..283a0ab57 100644 --- a/tools/oldest_constraints.txt +++ b/tools/oldest_constraints.txt @@ -1,98 +1,100 @@ # This file was generated by tools/pinning/oldest/repin.sh and can be updated using # that script. -apacheconfig==0.3.2 ; python_full_version < "3.8.0" and python_version >= "3.7" -asn1crypto==0.24.0 ; python_full_version >= "3.7.0" and python_full_version < "3.8.0" -astroid==2.15.6 ; python_full_version >= "3.7.2" and python_full_version < "3.8.0" -boto3==1.15.15 ; python_full_version < "3.8.0" and python_version >= "3.7" -botocore==1.18.15 ; python_full_version < "3.8.0" and python_version >= "3.7" -cachetools==5.3.1 ; python_version >= "3.7" and python_full_version < "3.8.0" -certifi==2023.7.22 ; python_full_version < "3.8.0" and python_version >= "3.7" -cffi==1.11.5 ; python_full_version < "3.8.0" and python_version >= "3.7" -chardet==3.0.4 ; python_full_version < "3.8.0" and python_version >= "3.7" -cloudflare==1.5.1 ; python_full_version < "3.8.0" and python_version >= "3.7" -colorama==0.4.6 ; python_full_version < "3.8.0" and sys_platform == "win32" and python_version >= "3.7" -configargparse==1.5.3 ; python_full_version < "3.8.0" and python_version >= "3.7" -configobj==5.0.6 ; python_full_version < "3.8.0" and python_version >= "3.7" -coverage==7.2.7 ; python_version >= "3.7" and python_full_version < "3.8.0" -cryptography==3.2.1 ; python_full_version < "3.8.0" and python_version >= "3.7" -cython==0.29.36 ; python_full_version >= "3.7.0" and python_full_version < "3.8.0" -dill==0.3.7 ; python_full_version >= "3.7.2" and python_full_version < "3.8.0" -distlib==0.3.7 ; python_version >= "3.7" and python_full_version < "3.8.0" -distro==1.0.1 ; python_full_version < "3.8.0" and python_version >= "3.7" -dns-lexicon==3.2.1 ; python_full_version < "3.8.0" and python_version >= "3.7" -dnspython==1.15.0 ; python_full_version < "3.8.0" and python_version >= "3.7" -exceptiongroup==1.1.3 ; python_version >= "3.7" and python_full_version < "3.8.0" -execnet==2.0.2 ; python_version >= "3.7" and python_full_version < "3.8.0" -filelock==3.12.2 ; python_version >= "3.7" and python_full_version < "3.8.0" -funcsigs==0.4 ; python_full_version >= "3.7.0" and python_full_version < "3.8.0" -future==0.18.3 ; python_full_version < "3.8.0" and python_version >= "3.7" -google-api-python-client==1.6.5 ; python_full_version < "3.8.0" and python_version >= "3.7" -google-auth==2.16.0 ; python_full_version < "3.8.0" and python_version >= "3.7" -httplib2==0.9.2 ; python_full_version < "3.8.0" and python_version >= "3.7" -idna==2.6 ; python_full_version < "3.8.0" and python_version >= "3.7" -importlib-metadata==6.7.0 ; python_version >= "3.7" and python_version < "3.8" -iniconfig==2.0.0 ; python_version >= "3.7" and python_full_version < "3.8.0" -ipaddress==1.0.16 ; python_full_version >= "3.7.0" and python_full_version < "3.8.0" -isort==5.11.5 ; python_full_version >= "3.7.2" and python_full_version < "3.8.0" -jmespath==0.10.0 ; python_full_version < "3.8.0" and python_version >= "3.7" -josepy==1.13.0 ; python_version >= "3.7" and python_full_version < "3.8.0" -lazy-object-proxy==1.9.0 ; python_full_version >= "3.7.2" and python_full_version < "3.8.0" -logger==1.4 ; python_full_version < "3.8.0" and python_version >= "3.7" -mccabe==0.7.0 ; python_full_version >= "3.7.2" and python_full_version < "3.8.0" -mypy-extensions==1.0.0 ; python_version >= "3.7" and python_full_version < "3.8.0" -mypy==1.4.1 ; python_version >= "3.7" and python_full_version < "3.8.0" -ndg-httpsclient==0.3.2 ; python_full_version >= "3.7.0" and python_full_version < "3.8.0" -oauth2client==4.1.3 ; python_full_version < "3.8.0" and python_version >= "3.7" -packaging==23.1 ; python_version >= "3.7" and python_full_version < "3.8.0" -parsedatetime==2.4 ; python_full_version < "3.8.0" and python_version >= "3.7" -pbr==1.8.0 ; python_full_version >= "3.7.0" and python_full_version < "3.8.0" -pip==23.2.1 ; python_version >= "3.7" and python_full_version < "3.8.0" -platformdirs==3.10.0 ; python_full_version < "3.8.0" and python_version >= "3.7" -pluggy==1.2.0 ; python_version >= "3.7" and python_full_version < "3.8.0" -ply==3.4 ; python_full_version < "3.8.0" and python_version >= "3.7" -py==1.11.0 ; python_version >= "3.7" and python_full_version < "3.8.0" -pyasn1-modules==0.3.0 ; python_full_version < "3.8.0" and python_version >= "3.7" -pyasn1==0.4.8 ; python_full_version < "3.8.0" and python_version >= "3.7" -pycparser==2.14 ; python_full_version < "3.8.0" and python_version >= "3.7" -pylint==2.17.5 ; python_full_version >= "3.7.2" and python_full_version < "3.8.0" -pyopenssl==17.5.0 ; python_full_version < "3.8.0" and python_version >= "3.7" -pyparsing==2.2.1 ; python_full_version < "3.8.0" and python_version >= "3.7" -pyrfc3339==1.0 ; python_full_version < "3.8.0" and python_version >= "3.7" -pytest-cov==4.1.0 ; python_version >= "3.7" and python_full_version < "3.8.0" -pytest-xdist==3.3.1 ; python_version >= "3.7" and python_full_version < "3.8.0" -pytest==7.4.0 ; python_version >= "3.7" and python_full_version < "3.8.0" -python-augeas==0.5.0 ; python_full_version < "3.8.0" and python_version >= "3.7" -python-dateutil==2.8.2 ; python_full_version < "3.8.0" and python_version >= "3.7" -python-digitalocean==1.11 ; python_full_version < "3.8.0" and python_version >= "3.7" -pytz==2019.3 ; python_full_version < "3.8.0" and python_version >= "3.7" -pywin32==306 ; python_version >= "3.7" and python_full_version < "3.8.0" and sys_platform == "win32" -pyyaml==6.0.1 ; python_full_version < "3.8.0" and python_version >= "3.7" -requests-file==1.5.1 ; python_version >= "3.7" and python_full_version < "3.8.0" -requests==2.20.0 ; python_full_version < "3.8.0" and python_version >= "3.7" -rsa==4.9 ; python_full_version < "3.8.0" and python_version >= "3.7" -s3transfer==0.3.7 ; python_full_version < "3.8.0" and python_version >= "3.7" -setuptools==41.6.0 ; python_full_version < "3.8.0" and python_version >= "3.7" -six==1.11.0 ; python_full_version < "3.8.0" and python_version >= "3.7" -tldextract==3.4.4 ; python_version >= "3.7" and python_full_version < "3.8.0" -tomli==2.0.1 ; python_full_version < "3.8.0" and python_version >= "3.7" -tomlkit==0.12.1 ; python_full_version >= "3.7.2" and python_full_version < "3.8.0" -tox==1.9.2 ; python_version >= "3.7" and python_full_version < "3.8.0" -typed-ast==1.5.5 ; python_version < "3.8" and python_version >= "3.7" -types-cryptography==3.3.23.2 ; python_version >= "3.7" and python_full_version < "3.8.0" -types-httplib2==0.22.0.2 ; python_version >= "3.7" and python_full_version < "3.8.0" -types-pyopenssl==23.0.0.0 ; python_version >= "3.7" and python_full_version < "3.8.0" -types-pyrfc3339==1.1.1.5 ; python_version >= "3.7" and python_full_version < "3.8.0" -types-python-dateutil==2.8.19.14 ; python_version >= "3.7" and python_full_version < "3.8.0" -types-pytz==2023.3.0.1 ; python_version >= "3.7" and python_full_version < "3.8.0" -types-pywin32==306.0.0.4 ; python_version >= "3.7" and python_full_version < "3.8.0" -types-requests==2.31.0.2 ; python_version >= "3.7" and python_full_version < "3.8.0" -types-setuptools==68.1.0.0 ; python_version >= "3.7" and python_full_version < "3.8.0" -types-six==1.16.21.9 ; python_version >= "3.7" and python_full_version < "3.8.0" -types-urllib3==1.26.25.14 ; python_version >= "3.7" and python_full_version < "3.8.0" -typing-extensions==4.7.1 ; python_version < "3.8" and python_version >= "3.7" -uritemplate==3.0.1 ; python_full_version < "3.8.0" and python_version >= "3.7" -urllib3==1.24.2 ; python_full_version < "3.8.0" and python_version >= "3.7" -virtualenv==20.24.3 ; python_version >= "3.7" and python_full_version < "3.8.0" -wheel==0.33.6 ; python_full_version < "3.8.0" and python_version >= "3.7" -wrapt==1.15.0 ; python_full_version >= "3.7.2" and python_full_version < "3.8.0" +apacheconfig==0.3.2 ; python_version >= "3.7" and python_version < "3.8" +appdirs==1.4.4 ; python_version >= "3.7" and python_version < "3.8" +asn1crypto==0.24.0 ; python_version >= "3.7" and python_version < "3.8" +astroid==2.15.6 ; python_full_version >= "3.7.2" and python_version < "3.8" +boto3==1.15.15 ; python_version >= "3.7" and python_version < "3.8" +botocore==1.18.15 ; python_version >= "3.7" and python_version < "3.8" +cachetools==5.3.1 ; python_version >= "3.7" and python_version < "3.8" +certifi==2023.7.22 ; python_version >= "3.7" and python_version < "3.8" +cffi==1.11.5 ; python_version >= "3.7" and python_version < "3.8" +chardet==3.0.4 ; python_version >= "3.7" and python_version < "3.8" +cloudflare==1.5.1 ; python_version >= "3.7" and python_version < "3.8" +colorama==0.4.6 ; python_version >= "3.7" and python_version < "3.8" and sys_platform == "win32" +configargparse==1.5.3 ; python_version >= "3.7" and python_version < "3.8" +configobj==5.0.6 ; python_version >= "3.7" and python_version < "3.8" +coverage==7.2.7 ; python_version >= "3.7" and python_version < "3.8" +cryptography==3.2.1 ; python_version >= "3.7" and python_version < "3.8" +cython==0.29.36 ; python_version >= "3.7" and python_version < "3.8" +dill==0.3.7 ; python_full_version >= "3.7.2" and python_version < "3.8" +distlib==0.3.7 ; python_version >= "3.7" and python_version < "3.8" +distro==1.0.1 ; python_version >= "3.7" and python_version < "3.8" +dns-lexicon==3.2.1 ; python_version >= "3.7" and python_version < "3.8" +dnspython==1.15.0 ; python_version >= "3.7" and python_version < "3.8" +exceptiongroup==1.1.3 ; python_version >= "3.7" and python_version < "3.8" +execnet==2.0.2 ; python_version >= "3.7" and python_version < "3.8" +filelock==3.12.2 ; python_version >= "3.7" and python_version < "3.8" +funcsigs==0.4 ; python_version >= "3.7" and python_version < "3.8" +future==0.18.3 ; python_version >= "3.7" and python_version < "3.8" +google-api-python-client==1.6.5 ; python_version >= "3.7" and python_version < "3.8" +google-auth==2.16.0 ; python_version >= "3.7" and python_version < "3.8" +httplib2==0.9.2 ; python_version >= "3.7" and python_version < "3.8" +idna==2.6 ; python_version >= "3.7" and python_version < "3.8" +importlib-metadata==4.6.4 ; python_version >= "3.7" and python_version < "3.8" +importlib-resources==5.12.0 ; python_version >= "3.7" and python_version < "3.8" +iniconfig==2.0.0 ; python_version >= "3.7" and python_version < "3.8" +ipaddress==1.0.16 ; python_version >= "3.7" and python_version < "3.8" +isort==5.11.5 ; python_full_version >= "3.7.2" and python_version < "3.8" +jmespath==0.10.0 ; python_version >= "3.7" and python_version < "3.8" +josepy==1.13.0 ; python_version >= "3.7" and python_version < "3.8" +lazy-object-proxy==1.9.0 ; python_full_version >= "3.7.2" and python_version < "3.8" +logger==1.4 ; python_version >= "3.7" and python_version < "3.8" +mccabe==0.7.0 ; python_full_version >= "3.7.2" and python_version < "3.8" +mypy-extensions==1.0.0 ; python_version >= "3.7" and python_version < "3.8" +mypy==1.4.1 ; python_version >= "3.7" and python_version < "3.8" +ndg-httpsclient==0.3.2 ; python_version >= "3.7" and python_version < "3.8" +oauth2client==4.1.3 ; python_version >= "3.7" and python_version < "3.8" +packaging==23.1 ; python_version >= "3.7" and python_version < "3.8" +parsedatetime==2.4 ; python_version >= "3.7" and python_version < "3.8" +pbr==1.8.0 ; python_version >= "3.7" and python_version < "3.8" +pip==23.2.1 ; python_version >= "3.7" and python_version < "3.8" +platformdirs==3.10.0 ; python_full_version >= "3.7.2" and python_version < "3.8" +pluggy==1.2.0 ; python_version >= "3.7" and python_version < "3.8" +ply==3.4 ; python_version >= "3.7" and python_version < "3.8" +py==1.11.0 ; python_version >= "3.7" and python_version < "3.8" +pyasn1-modules==0.3.0 ; python_version >= "3.7" and python_version < "3.8" +pyasn1==0.4.8 ; python_version >= "3.7" and python_version < "3.8" +pycparser==2.14 ; python_version >= "3.7" and python_version < "3.8" +pylint==2.17.5 ; python_full_version >= "3.7.2" and python_version < "3.8" +pyopenssl==17.5.0 ; python_version >= "3.7" and python_version < "3.8" +pyparsing==2.2.1 ; python_version >= "3.7" and python_version < "3.8" +pyrfc3339==1.0 ; python_version >= "3.7" and python_version < "3.8" +pytest-cov==4.1.0 ; python_version >= "3.7" and python_version < "3.8" +pytest-xdist==3.3.1 ; python_version >= "3.7" and python_version < "3.8" +pytest==7.4.2 ; python_version >= "3.7" and python_version < "3.8" +python-augeas==0.5.0 ; python_version >= "3.7" and python_version < "3.8" +python-dateutil==2.8.2 ; python_version >= "3.7" and python_version < "3.8" +python-digitalocean==1.11 ; python_version >= "3.7" and python_version < "3.8" +pytz==2019.3 ; python_version >= "3.7" and python_version < "3.8" +pywin32==306 ; python_version >= "3.7" and python_version < "3.8" and sys_platform == "win32" +pyyaml==6.0.1 ; python_version >= "3.7" and python_version < "3.8" +requests-file==1.5.1 ; python_version >= "3.7" and python_version < "3.8" +requests==2.20.0 ; python_version >= "3.7" and python_version < "3.8" +rsa==4.9 ; python_version >= "3.7" and python_version < "3.8" +s3transfer==0.3.7 ; python_version >= "3.7" and python_version < "3.8" +setuptools==41.6.0 ; python_version >= "3.7" and python_version < "3.8" +six==1.11.0 ; python_version >= "3.7" and python_version < "3.8" +tldextract==3.5.0 ; python_version >= "3.7" and python_version < "3.8" +tomli==2.0.1 ; python_version >= "3.7" and python_version < "3.8" +tomlkit==0.12.1 ; python_full_version >= "3.7.2" and python_version < "3.8" +tox==1.9.2 ; python_version >= "3.7" and python_version < "3.8" +typed-ast==1.5.5 ; python_version >= "3.7" and python_version < "3.8" +types-cryptography==3.3.23.2 ; python_version >= "3.7" and python_version < "3.8" +types-httplib2==0.22.0.2 ; python_version >= "3.7" and python_version < "3.8" +types-pyopenssl==23.0.0.0 ; python_version >= "3.7" and python_version < "3.8" +types-pyrfc3339==1.1.1.5 ; python_version >= "3.7" and python_version < "3.8" +types-python-dateutil==2.8.19.14 ; python_version >= "3.7" and python_version < "3.8" +types-pytz==2023.3.0.1 ; python_version >= "3.7" and python_version < "3.8" +types-pywin32==306.0.0.4 ; python_version >= "3.7" and python_version < "3.8" +types-requests==2.31.0.2 ; python_version >= "3.7" and python_version < "3.8" +types-setuptools==68.2.0.0 ; python_version >= "3.7" and python_version < "3.8" +types-six==1.16.21.9 ; python_version >= "3.7" and python_version < "3.8" +types-urllib3==1.26.25.14 ; python_version >= "3.7" and python_version < "3.8" +typing-extensions==4.7.1 ; python_version >= "3.7" and python_version < "3.8" +uritemplate==3.0.1 ; python_version >= "3.7" and python_version < "3.8" +urllib3==1.24.2 ; python_version >= "3.7" and python_version < "3.8" +virtualenv==20.4.7 ; python_version >= "3.7" and python_version < "3.8" +wheel==0.33.6 ; python_version >= "3.7" and python_version < "3.8" +wrapt==1.15.0 ; python_full_version >= "3.7.2" and python_version < "3.8" zipp==3.15.0 ; python_version >= "3.7" and python_version < "3.8" diff --git a/tools/pinning/current/pyproject.toml b/tools/pinning/current/pyproject.toml index 44d5dc9d1..9d0fb3136 100644 --- a/tools/pinning/current/pyproject.toml +++ b/tools/pinning/current/pyproject.toml @@ -75,6 +75,11 @@ poetry = "<1.3.0" # https://github.com/certbot/certbot/issues/9606. setuptools = "<67.5.0" +# Lexicon 3.14+ deprecates several private APIs and create stubs packages, leading +# to several warnings and lint/mypy errors. Let's pin it to 3.13 until this is fixed +# with https://github.com/certbot/certbot/pull/9746. +dns-lexicon = "<3.14" + [tool.poetry.dev-dependencies] [build-system] diff --git a/tools/pinning/oldest/pyproject.toml b/tools/pinning/oldest/pyproject.toml index 540ee6ac4..28e0ccabe 100644 --- a/tools/pinning/oldest/pyproject.toml +++ b/tools/pinning/oldest/pyproject.toml @@ -10,7 +10,7 @@ license = "Apache License 2.0" [tool.poetry.dependencies] # The Python version here should be kept in sync with the one used in our # oldest tests in tox.ini. -python = "3.7.*" +python = "<3.8 >= 3.7" # Local dependencies # Any local packages that have dependencies on other local packages must be @@ -61,6 +61,7 @@ google-api-python-client = "1.6.5" google-auth = "2.16.0" httplib2 = "0.9.2" idna = "2.6" +importlib-metadata = "4.6.4" ipaddress = "1.0.16" ndg-httpsclient = "0.3.2" parsedatetime = "2.4" diff --git a/tools/requirements.txt b/tools/requirements.txt index c02b82313..724975055 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -10,7 +10,7 @@ apacheconfig==0.3.2 ; python_version >= "3.7" and python_version < "4.0" appnope==0.1.3 ; python_version >= "3.7" and python_version < "4.0" and sys_platform == "darwin" astroid==2.13.5 ; python_full_version >= "3.7.2" and python_version < "4.0" attrs==23.1.0 ; python_version >= "3.7" and python_version < "4.0" -azure-core==1.29.3 ; python_version >= "3.7" and python_version < "4.0" +azure-core==1.29.4 ; python_version >= "3.7" and python_version < "4.0" azure-devops==7.1.0b3 ; python_version >= "3.7" and python_version < "4.0" babel==2.12.1 ; python_version >= "3.7" and python_version < "4.0" backcall==0.2.0 ; python_version >= "3.7" and python_version < "4.0" @@ -18,8 +18,8 @@ backports-cached-property==1.0.2 ; python_version >= "3.7" and python_version < bcrypt==4.0.1 ; python_version >= "3.7" and python_version < "4.0" beautifulsoup4==4.12.2 ; python_version >= "3.7" and python_version < "4.0" bleach==6.0.0 ; python_version >= "3.7" and python_version < "4.0" -boto3==1.27.1 ; python_version >= "3.7" and python_version < "4.0" -botocore==1.30.1 ; python_version >= "3.7" and python_version < "4.0" +boto3==1.28.43 ; python_version >= "3.7" and python_version < "4.0" +botocore==1.31.43 ; python_version >= "3.7" and python_version < "4.0" cachecontrol==0.12.14 ; python_version >= "3.7" and python_version < "4.0" cachetools==5.3.1 ; python_version >= "3.7" and python_version < "4.0" cachy==0.3.0 ; python_version >= "3.7" and python_version < "4.0" @@ -28,7 +28,7 @@ cffi==1.15.1 ; python_version >= "3.7" and python_version < "4.0" charset-normalizer==3.2.0 ; python_version >= "3.7" and python_version < "4.0" cleo==1.0.0a5 ; python_version >= "3.7" and python_version < "4.0" cloudflare==2.11.7 ; python_version >= "3.7" and python_version < "4.0" -colorama==0.4.6 ; python_version < "4.0" and sys_platform == "win32" and python_version >= "3.7" or python_version >= "3.7" and python_version < "4.0" and platform_system == "Windows" +colorama==0.4.6 ; python_version >= "3.7" and python_version < "4.0" and (sys_platform == "win32" or platform_system == "Windows") configargparse==1.7 ; python_version >= "3.7" and python_version < "4.0" configobj==5.0.8 ; python_version >= "3.7" and python_version < "4.0" coverage==7.2.7 ; python_version >= "3.7" and python_version < "4.0" @@ -42,14 +42,14 @@ distlib==0.3.7 ; python_version >= "3.7" and python_version < "4.0" distro==1.8.0 ; python_version >= "3.7" and python_version < "4.0" dns-lexicon==3.13.0 ; python_version >= "3.7" and python_version < "4.0" dnspython==2.3.0 ; python_version >= "3.7" and python_version < "4.0" -docutils==0.18.1 ; python_version >= "3.7" and python_version < "4.0" +docutils==0.19 ; python_version >= "3.7" and python_version < "4.0" dulwich==0.20.50 ; python_version >= "3.7" and python_version < "4.0" exceptiongroup==1.1.3 ; python_version >= "3.7" and python_version < "3.11" execnet==2.0.2 ; python_version >= "3.7" and python_version < "4.0" -fabric==3.2.1 ; python_version >= "3.7" and python_version < "4.0" +fabric==3.2.2 ; python_version >= "3.7" and python_version < "4.0" filelock==3.12.2 ; python_version >= "3.7" and python_version < "4.0" google-api-core==2.11.1 ; python_version >= "3.7" and python_version < "4.0" -google-api-python-client==2.97.0 ; python_version >= "3.7" and python_version < "4.0" +google-api-python-client==2.98.0 ; python_version >= "3.7" and python_version < "4.0" google-auth-httplib2==0.1.0 ; python_version >= "3.7" and python_version < "4.0" google-auth==2.22.0 ; python_version >= "3.7" and python_version < "4.0" googleapis-common-protos==1.60.0 ; python_version >= "3.7" and python_version < "4.0" @@ -58,7 +58,7 @@ httplib2==0.22.0 ; python_version >= "3.7" and python_version < "4.0" idna==3.4 ; python_version >= "3.7" and python_version < "4.0" imagesize==1.4.1 ; python_version >= "3.7" and python_version < "4.0" importlib-metadata==4.13.0 ; python_version >= "3.7" and python_version < "4.0" -importlib-resources==5.12.0 ; python_version >= "3.7" and python_version < "3.9" +importlib-resources==5.12.0 ; python_version >= "3.7" and python_version < "4.0" iniconfig==2.0.0 ; python_version >= "3.7" and python_version < "4.0" invoke==2.2.0 ; python_version >= "3.7" and python_version < "4.0" ipdb==0.13.13 ; python_version >= "3.7" and python_version < "4.0" @@ -97,14 +97,14 @@ pickleshare==0.7.5 ; python_version >= "3.7" and python_version < "4.0" pip==23.2.1 ; python_version >= "3.7" and python_version < "4.0" pkginfo==1.9.6 ; python_version >= "3.7" and python_version < "4.0" pkgutil-resolve-name==1.3.10 ; python_version >= "3.7" and python_version < "3.9" -platformdirs==2.6.2 ; python_version < "4.0" and python_version >= "3.7" +platformdirs==2.6.2 ; python_version >= "3.7" and python_version < "4.0" pluggy==1.2.0 ; python_version >= "3.7" and python_version < "4.0" ply==3.11 ; python_version >= "3.7" and python_version < "4.0" poetry-core==1.3.2 ; python_version >= "3.7" and python_version < "4.0" poetry-plugin-export==1.2.0 ; python_version >= "3.7" and python_version < "4.0" poetry==1.2.2 ; python_version >= "3.7" and python_version < "4.0" prompt-toolkit==3.0.39 ; python_version >= "3.7" and python_version < "4.0" -protobuf==4.24.1 ; python_version >= "3.7" and python_version < "4.0" +protobuf==4.24.3 ; python_version >= "3.7" and python_version < "4.0" ptyprocess==0.7.0 ; python_version >= "3.7" and python_version < "4.0" py==1.11.0 ; python_version >= "3.7" and python_version < "4.0" pyasn1-modules==0.3.0 ; python_version >= "3.7" and python_version < "4.0" @@ -121,11 +121,11 @@ pyrfc3339==1.1 ; python_version >= "3.7" and python_version < "4.0" pyrsistent==0.19.3 ; python_version >= "3.7" and python_version < "4.0" pytest-cov==4.1.0 ; python_version >= "3.7" and python_version < "4.0" pytest-xdist==3.3.1 ; python_version >= "3.7" and python_version < "4.0" -pytest==7.4.0 ; python_version >= "3.7" and python_version < "4.0" +pytest==7.4.2 ; python_version >= "3.7" and python_version < "4.0" python-augeas==1.1.0 ; python_version >= "3.7" and python_version < "4.0" python-dateutil==2.8.2 ; python_version >= "3.7" and python_version < "4.0" python-digitalocean==1.17.0 ; python_version >= "3.7" and python_version < "4.0" -pytz==2023.3 ; python_version >= "3.7" and python_version < "4.0" +pytz==2023.3.post1 ; python_version >= "3.7" and python_version < "4.0" pywin32-ctypes==0.2.2 ; python_version >= "3.7" and python_version < "4.0" and sys_platform == "win32" pywin32==306 ; python_version >= "3.7" and python_version < "4.0" and sys_platform == "win32" pyyaml==6.0.1 ; python_version >= "3.7" and python_version < "4.0" @@ -147,22 +147,21 @@ shellingham==1.5.3 ; python_version >= "3.7" and python_version < "4.0" six==1.16.0 ; python_version >= "3.7" and python_version < "4.0" snowballstemmer==2.2.0 ; python_version >= "3.7" and python_version < "4.0" soupsieve==2.4.1 ; python_version >= "3.7" and python_version < "4.0" -sphinx-rtd-theme==1.3.0 ; python_version >= "3.7" and python_version < "4.0" +sphinx-rtd-theme==0.5.1 ; python_version >= "3.7" and python_version < "4.0" sphinx==5.3.0 ; python_version >= "3.7" and python_version < "4.0" sphinxcontrib-applehelp==1.0.2 ; python_version >= "3.7" and python_version < "4.0" sphinxcontrib-devhelp==1.0.2 ; python_version >= "3.7" and python_version < "4.0" sphinxcontrib-htmlhelp==2.0.0 ; python_version >= "3.7" and python_version < "4.0" -sphinxcontrib-jquery==4.1 ; python_version >= "3.7" and python_version < "4.0" sphinxcontrib-jsmath==1.0.1 ; python_version >= "3.7" and python_version < "4.0" sphinxcontrib-qthelp==1.0.3 ; python_version >= "3.7" and python_version < "4.0" sphinxcontrib-serializinghtml==1.1.5 ; python_version >= "3.7" and python_version < "4.0" -tldextract==3.4.4 ; python_version >= "3.7" and python_version < "4.0" +tldextract==3.5.0 ; python_version >= "3.7" and python_version < "4.0" tomli==2.0.1 ; python_version >= "3.7" and python_full_version <= "3.11.0a6" -tomlkit==0.12.1 ; python_version < "4.0" and python_version >= "3.7" +tomlkit==0.12.1 ; python_version >= "3.7" and python_version < "4.0" tox==3.28.0 ; python_version >= "3.7" and python_version < "4.0" traitlets==5.9.0 ; python_version >= "3.7" and python_version < "4.0" twine==4.0.2 ; python_version >= "3.7" and python_version < "4.0" -typed-ast==1.5.5 ; python_version < "3.8" and python_version >= "3.7" +typed-ast==1.5.5 ; python_version >= "3.7" and python_version < "3.8" types-httplib2==0.22.0.2 ; python_version >= "3.7" and python_version < "4.0" types-pyopenssl==23.2.0.2 ; python_version >= "3.7" and python_version < "4.0" types-pyrfc3339==1.1.1.5 ; python_version >= "3.7" and python_version < "4.0" @@ -170,7 +169,7 @@ types-python-dateutil==2.8.19.14 ; python_version >= "3.7" and python_version < types-pytz==2023.3.0.1 ; python_version >= "3.7" and python_version < "4.0" types-pywin32==306.0.0.4 ; python_version >= "3.7" and python_version < "4.0" types-requests==2.31.0.2 ; python_version >= "3.7" and python_version < "4.0" -types-setuptools==68.1.0.0 ; python_version >= "3.7" and python_version < "4.0" +types-setuptools==68.2.0.0 ; python_version >= "3.7" and python_version < "4.0" types-six==1.16.21.9 ; python_version >= "3.7" and python_version < "4.0" types-urllib3==1.26.25.14 ; python_version >= "3.7" and python_version < "4.0" typing-extensions==4.7.1 ; python_version >= "3.7" and python_version < "4.0" @@ -180,7 +179,7 @@ virtualenv==20.21.1 ; python_version >= "3.7" and python_version < "4.0" wcwidth==0.2.6 ; python_version >= "3.7" and python_version < "4.0" webencodings==0.5.1 ; python_version >= "3.7" and python_version < "4.0" wheel==0.41.2 ; python_version >= "3.7" and python_version < "4.0" -wrapt==1.15.0 ; python_version < "4.0" and python_version >= "3.7" +wrapt==1.15.0 ; python_version >= "3.7" and python_version < "4.0" xattr==0.9.9 ; python_version >= "3.7" and python_version < "4.0" and sys_platform == "darwin" yarg==0.1.9 ; python_version >= "3.7" and python_version < "4.0" zipp==3.15.0 ; python_version >= "3.7" and python_version < "4.0"