From 8b9bc1394936c768f138b613d1f9d7bf1b7d6b6a Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 6 Jul 2021 12:17:57 -0700 Subject: [PATCH] undo some stuff --- certbot/docs/contributing.rst | 45 ---------- tools/dev_constraints.txt | 131 +++++++++++++++++++++++++++++ tools/merge_requirements.py | 92 +++++++++++++++++++++ tools/oldest_constraints.txt | 151 ++++++++++++++-------------------- tools/pinning/pin.sh | 48 +++++++++++ tools/requirements.txt | 15 ++-- 6 files changed, 343 insertions(+), 139 deletions(-) create mode 100644 tools/dev_constraints.txt create mode 100755 tools/merge_requirements.py create mode 100755 tools/pinning/pin.sh diff --git a/certbot/docs/contributing.rst b/certbot/docs/contributing.rst index 000208c35..94e35f1bf 100644 --- a/certbot/docs/contributing.rst +++ b/certbot/docs/contributing.rst @@ -566,51 +566,6 @@ and run the command: This would generate the HTML documentation in ``_build/html`` in your current ``docs/`` directory. -Certbot's dependencies -====================== - -We attempt to pin all of Certbot's dependencies whenever we can for reliability -and consistency. Some of the places we have Certbot's dependencies pinned -include our snaps, Docker images, Windows installer, CI, and our development -environments. - -In most cases, the file where dependency versions are specified is -``tools/requirements.txt``. There are two exceptions to this. The first is our -"oldest" tests where ``tools/oldest_constraints.txt`` is used instead. The -purpose of the "oldest" tests is to ensure Certbot continues to work with the -oldest versions of our dependencies which we claim to support. The second -exception to using ``tools/requirements.txt`` is in our unpinned tests. As of -writing this, there is one test we run nightly in CI where we leave Certbot's -dependencies unpinned. The thinking behind this test is to help us learn about -breaking changes in our dependencies so that we can respond accordingly. - -The choices of whether Certbot's dependencies are pinned and what file is used -if they are should be automatically handled for you most of the time by -Certbot's tooling. The way it works though is ``tools/pip_install.py`` (which -many of our other tools build on) checks for the presence of environment -variables. If ``CERTBOT_NO_PIN`` is set to 1, Certbot's dependencies will not -be pinned. If that variable is not set and ``CERTBOT_OLDEST`` is set to 1, -``tools/oldest_constraints.txt`` will be used as constraints for ``pip``. -Otherwise, ``tools/requirements.txt`` is used as constraints. - -Updating dependency versions ----------------------------- - -``tools/requirements.txt`` and ``tools/oldest_constraints.txt`` can be updated -using ``tools/pinning/normal/repin.sh`` and ``tools/pinning/oldest/repin.sh`` -respectively. This works by using ``poetry`` to generate pinnings based on a -dummy Poetry project defined by the ``pyproject.toml`` file in the same -directory as the script. In many cases, you can just run the script to generate -updated dependencies, however, if you need to pin back packages or unpin -packages that were previously restricted to an older version, you will need to -modify the ``pyproject.toml`` file. The syntax used by this file is described -at https://python-poetry.org/docs/pyproject/ and how dependencies are specified -in this file is further described at -https://python-poetry.org/docs/dependency-specification/. - -If you want to learn more about the design used here, see -``tools/pinning/DESIGN.md`` in the Certbot repo. - .. _docker-dev: Running the client with Docker diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt new file mode 100644 index 000000000..50b52222d --- /dev/null +++ b/tools/dev_constraints.txt @@ -0,0 +1,131 @@ +# Specifies extra Python package versions during our tests with the oldest +# supported versions of our dependencies. Some dev package versions specified +# here may be overridden by higher level constraints files during tests (eg. +# tools/oldest_constraints.txt). +alabaster==0.7.10 +apacheconfig==0.3.2 +apipkg==1.4 +appnope==0.1.0 +asn1crypto==0.22.0 +astroid==2.3.3 +attrs==17.3.0 +azure-devops==6.0.0b2 +Babel==2.5.1 +backcall==0.2.0 +backports.functools-lru-cache==1.5 +backports.shutil-get-terminal-size==1.0.0 +backports.ssl-match-hostname==3.7.0.1 +bcrypt==3.1.6 +boto3==1.17.4 +botocore==1.20.4 +cached-property==1.5.1 +cloudflare==2.3.1 +configparser==3.7.4 +contextlib2==0.6.0.post1 +coverage==4.5.4 +decorator==4.4.1 +deprecated==1.2.10 +dns-lexicon==3.3.17 +dnspython==2.1.0 +docker==4.3.1 +docker-compose==1.26.2 +docker-pycreds==0.4.0 +dockerpty==0.4.1 +docopt==0.6.2 +docutils==0.15.2 +execnet==1.5.0 +functools32==3.2.3.post2 +future==0.16.0 +futures==3.3.0 +filelock==3.0.12 +google-api-python-client==1.5.5 +httplib2==0.18.1 +imagesize==0.7.1 +importlib-metadata==0.23 +ipdb==0.12.3 +ipython==7.9.0 +ipython-genutils==0.2.0 +isodate==0.6.0 +isort==4.3.21 +jedi==0.17.1 +Jinja2==2.9.6 +jmespath==0.9.4 +josepy==1.1.0 +jsonpickle==2.0.0 +jsonschema==2.6.0 +lazy-object-proxy==1.4.3 +logger==1.4 +logilab-common==1.4.1 +MarkupSafe==1.1.1 +mccabe==0.6.1 +more-itertools==5.0.0 +msrest==0.6.18 +mypy==0.812 +mypy-extensions==0.4.3 +ndg-httpsclient==0.3.2 +oauth2client==4.0.0 +oauthlib==3.1.0 +packaging==19.2 +paramiko==2.4.2 +parso==0.7.0 +pathlib2==2.3.5 +pexpect==4.7.0 +pickleshare==0.7.5 +pip==20.2.4 +pkginfo==1.4.2 +pluggy==0.13.0 +ply==3.4 +prompt-toolkit==2.0.10 +ptyprocess==0.6.0 +py==1.8.0 +pyasn1==0.1.9 +pyasn1-modules==0.0.10 +PyGithub==1.52 +Pygments==2.2.0 +pyjwt==1.7.1 +pylint==2.4.3 +pynacl==1.3.0 +# If pynsist version is upgraded, our NSIS template windows-installer/template.nsi +# must be upgraded if necessary using the new built-in one from pynsist. +pynsist==2.7 +pytest==3.2.5 +pytest-cov==2.5.1 +pytest-forked==0.2 +pytest-xdist==1.22.5 +pytest-sugar==0.9.2 +pytest-rerunfailures==4.2 +python-dateutil==2.8.1 +python-digitalocean==1.15.0 +python-dotenv==0.14.0 +pywin32==300 +PyYAML==5.3.1 +repoze.sphinx.autointerface==0.8 +requests-file==1.4.2 +requests-oauthlib==1.3.0 +requests-toolbelt==0.8.0 +rsa==3.4.2 +s3transfer==0.3.1 +scandir==1.10.0 +simplegeneric==0.8.1 +singledispatch==3.4.0.3 +snowballstemmer==1.2.1 +Sphinx==1.7.5 +sphinx-rtd-theme==0.2.4 +sphinxcontrib-websupport==1.0.1 +texttable==0.9.1 +tldextract==2.2.0 +toml==0.10.0 +tox==3.14.0 +tqdm==4.19.4 +traitlets==4.3.3 +twine==1.11.0 +typed-ast==1.4.1 +typing==3.6.4 +typing-extensions==3.7.4.3 +uritemplate==3.0.0 +virtualenv==16.6.2 +wcwidth==0.1.8 +websocket-client==0.56.0 +wheel==0.35.1 +wrapt==1.11.2 +zipp==0.6.0 diff --git a/tools/merge_requirements.py b/tools/merge_requirements.py new file mode 100755 index 000000000..44a61249b --- /dev/null +++ b/tools/merge_requirements.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python +"""Merges multiple Python requirements files into one file. + +Requirements files specified later take precedence over earlier ones. +Only the simple formats SomeProject==1.2.3 or SomeProject<=1.2.3 are +currently supported. + +""" +import sys + + +def process_entries(entries): + """ + Ignore empty lines, comments and editable requirements + + :param list entries: List of entries + + :returns: mapping from a project to its version specifier + :rtype: dict + """ + data = {} + for e in entries: + e = e.strip() + if e and not e.startswith('#') and not e.startswith('-e'): + # Support for <= was added as part of + # https://github.com/certbot/certbot/pull/8460 because we weren't + # able to pin a package to an exact version. Normally, this + # functionality shouldn't be needed so we could remove it in the + # future. If you do so, make sure to update other places in this + # file related to this behavior such as this file's docstring. + for comparison in ('==', '<=',): + parts = e.split(comparison) + if len(parts) == 2: + project_name = parts[0] + version = parts[1] + data[project_name] = comparison + version + break + else: + raise ValueError("Unexpected syntax '{0}'".format(e)) + return data + +def read_file(file_path): + """Reads in a Python requirements file. + + :param str file_path: path to requirements file + + :returns: list of entries in the file + :rtype: list + + """ + with open(file_path) as file_h: + return file_h.readlines() + +def output_requirements(requirements): + """Prepare print requirements to stdout. + + :param dict requirements: mapping from a project to its version + specifier + + """ + return '\n'.join('{0}{1}'.format(key, value) + for key, value in sorted(requirements.items())) + + +def main(*paths): + """Merges multiple requirements files together and prints the result. + + Requirement files specified later in the list take precedence over earlier + files. Files are read from file paths passed from the command line arguments. + + If no command line arguments are defined, data is read from stdin instead. + + :param tuple paths: paths to requirements files provided on command line + + """ + data = {} + if paths: + for path in paths: + data.update(process_entries(read_file(path))) + else: + # Need to check if interactive to avoid blocking if nothing is piped + if not sys.stdin.isatty(): + stdin_data = [] + for line in sys.stdin: + stdin_data.append(line) + data.update(process_entries(stdin_data)) + + return output_requirements(data) + + +if __name__ == '__main__': + print(main(*sys.argv[1:])) # pylint: disable=star-args diff --git a/tools/oldest_constraints.txt b/tools/oldest_constraints.txt index d9ecf8c30..c31c5d4f8 100644 --- a/tools/oldest_constraints.txt +++ b/tools/oldest_constraints.txt @@ -1,100 +1,77 @@ -# This file was generated by tools/pinning/oldest/repin.sh and can be updated using -# that script. -apacheconfig==0.3.2 -asn1crypto==0.24.0 -astroid==2.5.6; python_version >= "3.6" and python_version < "4.0" -atomicwrites==1.4.0; python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.6" and python_full_version >= "3.4.0" -attrs==21.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -bcrypt==3.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -boto3==1.4.7 -botocore==1.7.41 -cached-property==1.5.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" +# This file contains the oldest versions of our dependencies we're trying to +# support. Usually these version numbers are taken from the packages of our +# dependencies available in popular LTS Linux distros. Keeping compatibility +# with those versions makes it much easier for OS maintainers to update their +# Certbot packages. +# +# When updating these dependencies, we should try to only update them to the +# oldest version of the package that is found in a non-EOL'd version of +# CentOS, Debian, or Ubuntu that has Certbot packages in their OS repositories +# using a version of Python we support. If the distro is EOL'd or using a +# version of Python we don't support, it can be ignored. + +# CentOS/RHEL 7 EPEL constraints +# Some of these constraints may be stricter than necessary because they +# initially referred to the Python 2 packages in CentOS/RHEL 7 with EPEL. cffi==1.9.1 chardet==2.2.1 -cloudflare==1.5.1 -colorama==0.4.4; python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" or python_full_version >= "3.5.0" and python_version >= "3.6" and sys_platform == "win32" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" and sys_platform == "win32" or python_version >= "3.6" and python_version < "4.0" and sys_platform == "win32" and python_full_version >= "3.5.0" -configargparse==0.10.0 -configobj==5.0.6 -coverage==5.5; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.6" -cryptography==2.1.4 -cython==0.29.23; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0") -distro==1.0.1 -dns-lexicon==3.2.1 -dnspython==2.1.0; python_version >= "3.6" -docker-compose==1.24.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -docker-pycreds==0.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -docker==3.7.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -dockerpty==0.4.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -docopt==0.6.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -docutils==0.17.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -execnet==1.9.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -filelock==3.0.12; python_version >= "3.6" -funcsigs==0.4 -future==0.18.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" -google-api-python-client==1.5.5 -httplib2==0.9.2 -idna==2.6 -importlib-metadata==4.5.0; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.4.0" and python_version >= "3.6" and python_version < "3.8" or python_version < "3.8" and python_version >= "3.6" -iniconfig==1.1.1; python_version >= "3.6" ipaddress==1.0.16 -isort==5.8.0; python_version >= "3.6" and python_version < "4.0" -jmespath==0.10.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" -josepy==1.8.0; python_version >= "3.6" -jsonschema==2.6.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -lazy-object-proxy==1.6.0; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.6.0" -logger==1.4; python_version >= "3.6" -mccabe==0.6.1; python_version >= "3.6" and python_version < "4.0" mock==1.0.1 -mypy-extensions==0.4.3; python_version >= "3.6" -mypy==0.910; python_version >= "3.6" ndg-httpsclient==0.3.2 -oauth2client==4.0.0 -packaging==20.9; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -paramiko==2.4.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -parsedatetime==2.4 -pbr==1.8.0 -pip==20.2.4; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") -pluggy==0.13.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" ply==3.4 -py==1.10.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" or python_full_version >= "3.5.0" and python_version >= "3.6" -pyasn1-modules==0.0.10; python_version >= "3.6" +pyOpenSSL==17.3.0 pyasn1==0.1.9 pycparser==2.14 -pylint==2.8.3; python_version >= "3.6" and python_version < "4.0" -pynacl==1.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -pyopenssl==17.3.0 -pyparsing==2.2.0 -pypiwin32==223; sys_platform == "win32" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6") -pyrfc3339==1.0 -pytest-cov==2.12.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -pytest-forked==1.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -pytest-xdist==2.3.0; python_version >= "3.6" or python_version >= "3.6" -pytest==6.2.4; python_version >= "3.6" or python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" +pyRFC3339==1.0 python-augeas==0.5.0 -python-dateutil==2.8.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" -python-digitalocean==1.11 -pytz==2012c -pywin32==301; sys_platform == "win32" and python_version >= "3.6" or sys_platform == "win32" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6") -pyyaml==3.13; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" or python_version >= "3.6" -requests-file==1.5.1; python_version >= "3.6" -requests-toolbelt==0.9.1; python_version >= "3.6" -requests==2.14.2 -rsa==4.7.2; python_version >= "3.6" and python_version < "4" -s3transfer==0.1.13; python_version >= "3.6" -setuptools==39.0.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0") -six==1.11.0 -texttable==0.9.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -tldextract==3.1.0; python_version >= "3.6" -toml==0.10.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" or python_full_version >= "3.5.0" and python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.3.0" -typed-ast==1.4.3; python_version >= "3.6" and python_version < "3.8" or implementation_name == "cpython" and python_version < "3.8" and python_version >= "3.6" -typing-extensions==3.10.0.0; python_version >= "3.6" or python_version < "3.8" and python_version >= "3.6" -uritemplate==3.0.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" +oauth2client==4.0.0 urllib3==1.10.2 -websocket-client==0.59.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -wheel==0.33.6; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") -wrapt==1.12.1; python_version >= "3.6" and python_version < "4.0" -zipp==3.4.1; python_version < "3.8" and python_version >= "3.6" zope.component==4.1.0 zope.event==4.0.3 -zope.hookable==4.0.4 zope.interface==4.0.5 + +# Debian Jessie Backports constraints +# Debian Jessie has reached end of life so these dependencies can probably be +# updated as needed or desired. +colorama==0.3.2 +enum34==1.0.3 +html5lib==0.999 +pbr==1.8.0 +pytz==2012rc0 + +# Debian Buster constraints +google-api-python-client==1.5.5 +pyparsing==2.2.0 + +# Our setup.py constraints +apacheconfig==0.3.2 +cloudflare==1.5.1 +python-digitalocean==1.11 +requests==2.6.0 + +# Ubuntu Xenial constraints +# Ubuntu Xenial only has versions of Python which we do not support available +# so these dependencies can probably be updated as needed or desired. +ConfigArgParse==0.10.0 +funcsigs==0.4 +zope.hookable==4.0.4 + +# Ubuntu Bionic constraints. +cryptography==2.1.4 +distro==1.0.1 +httplib2==0.9.2 +idna==2.6 +setuptools==39.0.1 +six==1.11.0 + +# Ubuntu Focal constraints +asn1crypto==0.24.0 +configobj==5.0.6 +parsedatetime==2.4 + +# Plugin constraints +# These aren't necessarily the oldest versions we need to support +# Tracking at https://github.com/certbot/certbot/issues/6473 +boto3==1.4.7 +botocore==1.7.41 +dns-lexicon==3.1.0 diff --git a/tools/pinning/pin.sh b/tools/pinning/pin.sh new file mode 100755 index 000000000..1c8111e1b --- /dev/null +++ b/tools/pinning/pin.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# This script accepts no arguments and automates the process of updating +# Certbot's dependencies. Dependencies can be pinned to older versions by +# modifying pyproject.toml in the same directory as this file. +set -euo pipefail + +WORK_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +REPO_ROOT="$(dirname "$(dirname "${WORK_DIR}")")" +RELATIVE_SCRIPT_PATH="$(realpath --relative-to "$REPO_ROOT" "$WORK_DIR")/$(basename "${BASH_SOURCE[0]}")" +REQUIREMENTS_FILE="$REPO_ROOT/tools/requirements.txt" + +if ! command -v poetry >/dev/null || [ $(poetry --version | grep -oE '[0-9]+\.[0-9]+' | sed 's/\.//') -lt 12 ]; then + echo "Please install poetry 1.2+." + echo "You may need to recreate Certbot's virtual environment and activate it." + exit 1 +fi + +# Old eggs can cause outdated dependency information to be used by poetry so we +# delete them before generating the lock file. See +# https://github.com/python-poetry/poetry/issues/4103 for more info. +cd "${REPO_ROOT}" +rm -rf */*.egg-info + +cd "${WORK_DIR}" + +if [ -f poetry.lock ]; then + rm poetry.lock +fi + +poetry lock + +TEMP_REQUIREMENTS=$(mktemp) +trap 'rm poetry.lock; rm $TEMP_REQUIREMENTS' EXIT + +poetry export -o "${TEMP_REQUIREMENTS}" --without-hashes +# We need to remove local packages from the requirements file. +sed -i '/^acme @/d; /certbot/d;' "${TEMP_REQUIREMENTS}" + +cat << EOF > "$REQUIREMENTS_FILE" +# This file was generated by $RELATIVE_SCRIPT_PATH and can be updated using +# that script. +# +# It is normally used as constraints to pip, however, it has the name +# requirements.txt so that is scanned by GitHub. See +# https://docs.github.com/en/github/visualizing-repository-data-with-graphs/about-the-dependency-graph#supported-package-ecosystems +# for more info. +EOF +cat "${TEMP_REQUIREMENTS}" >> "${REQUIREMENTS_FILE}" diff --git a/tools/requirements.txt b/tools/requirements.txt index 38ca2aaa1..7190c9c81 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -1,4 +1,4 @@ -# This file was generated by tools/pinning/normal/repin.sh and can be updated using +# This file was generated by tools/pinning/pin.sh and can be updated using # that script. # # It is normally used as constraints to pip, however, it has the name @@ -7,20 +7,21 @@ # for more info. alabaster==0.7.12; python_version >= "3.6" apacheconfig==0.3.2; python_version >= "3.6" +apipkg==1.5; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" appdirs==1.4.4; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" appnope==0.1.2; python_version == "3.6" and sys_platform == "darwin" or python_version >= "3.7" and sys_platform == "darwin" astroid==2.5.6; python_version >= "3.6" and python_version < "4.0" atomicwrites==1.4.0; python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.6" and python_full_version >= "3.4.0" attrs==21.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -awscli==1.19.95; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0") +awscli==1.19.91; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0") azure-devops==6.0.0b4; python_version >= "3.6" babel==2.9.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" backcall==0.2.0; python_version == "3.6" or python_version >= "3.7" bcrypt==3.2.0; python_version >= "3.6" beautifulsoup4==4.9.3; python_version >= "3.6" and python_version < "4.0" or python_version >= "3.6" bleach==3.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" -boto3==1.17.95; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" -botocore==1.20.95; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" +boto3==1.17.91; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" +botocore==1.20.91; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" cachecontrol==0.12.6; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" cached-property==1.5.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" cachetools==4.2.2; python_version >= "3.5" and python_version < "4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") @@ -50,11 +51,11 @@ dockerpty==0.4.1; python_version >= "3.6" and python_full_version < "3.0.0" or p docopt==0.6.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" docutils==0.15.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" or python_version >= "3.6" and python_full_version >= "3.3.0" entrypoints==0.3; python_version >= "3.6" and python_version < "4.0" -execnet==1.9.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" +execnet==1.8.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" fabric==2.6.0; python_version >= "3.6" filelock==3.0.12; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" or python_version >= "3.6" and python_version < "4.0" google-api-core==1.30.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" -google-api-python-client==2.9.0; python_version >= "3.6" +google-api-python-client==2.8.0; python_version >= "3.6" google-auth-httplib2==0.1.0; python_version >= "3.6" google-auth==1.31.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" googleapis-common-protos==1.53.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" @@ -166,7 +167,7 @@ tldextract==3.1.0; python_version >= "3.6" and python_version < "4.0" toml==0.10.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" or python_full_version >= "3.5.0" and python_version >= "3.6" or python_version == "3.6" and python_full_version < "3.0.0" or python_version > "3.6" and python_full_version < "3.0.0" or python_version == "3.6" and python_full_version >= "3.3.0" or python_version > "3.6" and python_full_version >= "3.3.0" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.3.0" tomlkit==0.7.2; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.5.0" tox==3.23.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0" -tqdm==4.61.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" +tqdm==4.61.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" traitlets==4.3.3 twine==3.3.0; python_version >= "3.6" typed-ast==1.4.3; python_version >= "3.6" or implementation_name == "cpython" and python_version < "3.8" and python_version >= "3.6"