Merge branch 'master' of https://github.com/certbot/certbot into docs/yaml-config

This commit is contained in:
zoracon 2023-09-20 12:55:59 -07:00
commit 4178e8ffc4
No known key found for this signature in database
GPG key ID: EF9F92E86D8CF643
31 changed files with 485 additions and 357 deletions

View file

@ -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):

View file

@ -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',

View file

@ -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)))

View file

@ -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] = [

View file

@ -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

View file

@ -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)

View file

@ -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',
]

View file

@ -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.

View file

@ -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:

View file

@ -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',

View file

@ -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"""

View file

@ -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

View file

@ -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),

View file

@ -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',

View file

@ -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:

View file

@ -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)

View file

@ -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)

View file

@ -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):

View file

@ -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',

View file

@ -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"

View file

@ -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):

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 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 "

View file

@ -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()

View file

@ -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

View file

@ -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()

View file

@ -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 = [

View file

@ -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

View file

@ -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"

View file

@ -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]

View file

@ -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"

View file

@ -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"