Repository: certbot/certbot (language: python)

Issue:
Fix lint and mypy with Python < 3.8
In https://github.com/certbot/certbot/pull/8748, we made a change that causes our lint and mypy tests to need to be run on Python 3.8+ to pass. See https://github.com/certbot/certbot/pull/8748#issuecomment-808790093 for the discussion of the problem here.

I don't think we should do this. Certbot supports Python 3.6+ and I think it could cause a particularly bad experience for new devs that don't happen to know they need Python 3.8+. This change also broke our development Dockerfile as can be seen at https://dev.azure.com/certbot/certbot/_build/results?buildId=3742&view=logs&j=bea2d267-f41e-5b33-7b51-a88065a8cbb0&t=0dc90756-6888-5ee6-5a6a-5855e6b9ae76&l=1873. Instead, I think we should change our approach here so the tests work on all versions of Python we support. I'm open to other ideas, but the two ideas I had for this are:

1. Just declare a runtime dependency on `typing-extensions`.
2. Add `typing-extensions` as a dev/test dependency and try to import it, but use similar fallback code to what we current have if it's not available.

What do you think @adferrand? Are you interested in working on this?


Discussion / hints:

Fix patch:
diff --git a/certbot/setup.py b/certbot/setup.py
index 8843a35a21e..6913d8384c7 100644
--- a/certbot/setup.py
+++ b/certbot/setup.py
@@ -77,6 +77,9 @@ def read_file(filename, encoding='utf8'):
     'pytest',
     'pytest-cov',
     'pytest-xdist',
+    # typing-extensions is required to import typing.Protocol and make the mypy checks
+    # pass (along with pylint about non-existent objects) on Python 3.6 & 3.7
+    'typing-extensions',
     'tox',
     'twine',
     'wheel',
diff --git a/tools/requirements.txt b/tools/requirements.txt
index 707cc64b9d1..e5a6d3be737 100644
--- a/tools/requirements.txt
+++ b/tools/requirements.txt
@@ -18,8 +18,8 @@ backcall==0.2.0
 bcrypt==3.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"
 beautifulsoup4==4.9.3; python_version >= "3.6" and python_version < "4.0"
 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.42; 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.42; 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.44; 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.44; 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" or python_version >= "3.6" and python_full_version >= "3.5.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.1; 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")
@@ -36,7 +36,7 @@ configobj==5.0.6; python_version >= "3.6"
 coverage==4.5.4; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0" and python_version < "4")
 crashtest==0.3.1; python_version >= "3.6" and python_version < "4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0")
 cryptography==3.4.7; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0") and sys_platform == "linux" or python_full_version >= "3.5.0" and python_version >= "3.6" and python_version < "4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0") and sys_platform == "linux"
-decorator==4.4.2; python_version == "3.6" and python_full_version < "3.0.0" or python_version == "3.6" and python_full_version >= "3.2.0"
+decorator==5.0.5
 deprecated==1.2.12; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0"
 distlib==0.3.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
 distro==1.5.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
@@ -99,7 +99,7 @@ ply==3.11; python_version >= "3.6"
 poetry-core==1.0.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
 poetry==1.1.5; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
 prompt-toolkit==3.0.3
-protobuf==3.15.6; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6"
+protobuf==3.15.7; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6"
 ptyprocess==0.7.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
 py==1.10.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
 pyasn1-modules==0.2.8; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6"
@@ -124,7 +124,7 @@ pytest==3.2.5
 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.6.0" and python_version >= "3.6"
 python-digitalocean==1.16.0; python_version >= "3.6"
-python-dotenv==0.16.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
+python-dotenv==0.17.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
 pytz==2021.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.6.0"
 pywin32-ctypes==0.2.0; python_version >= "3.6" and python_version < "4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0") and sys_platform == "win32"
 pywin32==300; 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.5.0" and python_version >= "3.6")
@@ -144,7 +144,7 @@ shellingham==1.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or
 six==1.15.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
 snowballstemmer==2.1.0; python_version >= "3.6"
 soupsieve==2.2.1; python_version >= "3.6"
-sphinx-rtd-theme==0.5.1; python_version >= "3.6"
+sphinx-rtd-theme==0.5.2; python_version >= "3.6"
 sphinx==3.5.3; python_version >= "3.6"
 sphinxcontrib-applehelp==1.0.2; python_version >= "3.6"
 sphinxcontrib-devhelp==1.0.2; python_version >= "3.6"


Test patch:
diff --git a/certbot/certbot/plugins/dns_test_common.py b/certbot/certbot/plugins/dns_test_common.py
index f6d7bdca8cf..7a8df9329da 100644
--- a/certbot/certbot/plugins/dns_test_common.py
+++ b/certbot/certbot/plugins/dns_test_common.py
@@ -12,7 +12,7 @@
 from certbot.tests import util as test_util
 
 if typing.TYPE_CHECKING:
-    from typing import Protocol
+    from typing_extensions import Protocol
 else:
     Protocol = object  # type: ignore
 
diff --git a/certbot/certbot/plugins/dns_test_common_lexicon.py b/certbot/certbot/plugins/dns_test_common_lexicon.py
index 48261d39599..5c6f09d208d 100644
--- a/certbot/certbot/plugins/dns_test_common_lexicon.py
+++ b/certbot/certbot/plugins/dns_test_common_lexicon.py
@@ -18,7 +18,7 @@
 except ImportError:  # pragma: no cover
     from unittest import mock  # type: ignore
 if typing.TYPE_CHECKING:
-    from typing import Protocol
+    from typing_extensions import Protocol
 else:
     Protocol = object  # type: ignore