Get mypy passing with check_untyped_defs everywhere
diff --git a/certbot-compatibility-test/certbot_compatibility_test/test_driver.py b/certbot-compatibility-test/certbot_compatibility_test/test_driver.py
index 2c6c917b329..9eea95e674c 100644
--- a/certbot-compatibility-test/certbot_compatibility_test/test_driver.py
+++ b/certbot-compatibility-test/certbot_compatibility_test/test_driver.py
@@ -15,6 +15,7 @@
 from acme import challenges
 from acme import crypto_util
 from acme import messages
+from acme.magic_typing import List, Tuple  # pylint: disable=unused-import, no-name-in-module
 from certbot import achallenges
 from certbot import errors as le_errors
 from certbot.tests import acme_util
@@ -52,9 +53,8 @@ def test_authenticator(plugin, config, temp_dir):
 
     try:
         responses = plugin.perform(achalls)
-    except le_errors.Error as error:
-        logger.error("Performing challenges on %s caused an error:", config)
-        logger.exception(error)
+    except le_errors.Error:
+        logger.error("Performing challenges on %s caused an error:", config, exc_info=True)
         return False
 
     success = True
@@ -82,9 +82,8 @@ def test_authenticator(plugin, config, temp_dir):
     if success:
         try:
             plugin.cleanup(achalls)
-        except le_errors.Error as error:
-            logger.error("Challenge cleanup for %s caused an error:", config)
-            logger.exception(error)
+        except le_errors.Error:
+            logger.error("Challenge cleanup for %s caused an error:", config, exc_info=True)
             success = False
 
         if _dirs_are_unequal(config, backup):
@@ -147,9 +146,8 @@ def test_deploy_cert(plugin, temp_dir, domains):
         try:
             plugin.deploy_cert(domain, cert_path, util.KEY_PATH, cert_path, cert_path)
             plugin.save()  # Needed by the Apache plugin
-        except le_errors.Error as error:
-            logger.error("**** Plugin failed to deploy certificate for %s:", domain)
-            logger.exception(error)
+        except le_errors.Error:
+            logger.error("**** Plugin failed to deploy certificate for %s:", domain, exc_info=True)
             return False
 
     if not _save_and_restart(plugin, "deployed"):
@@ -179,7 +177,7 @@ def test_enhancements(plugin, domains):
                      "enhancements")
         return False
 
-    domains_and_info = [(domain, []) for domain in domains]
+    domains_and_info = [(domain, []) for domain in domains]  # type: List[Tuple[str, List[bool]]]
 
     for domain, info in domains_and_info:
         try:
@@ -192,10 +190,9 @@ def test_enhancements(plugin, domains):
             # Don't immediately fail because a redirect may already be enabled
             logger.warning("*** Plugin failed to enable redirect for %s:", domain)
             logger.warning("%s", error)
-        except le_errors.Error as error:
+        except le_errors.Error:
             logger.error("*** An error occurred while enabling redirect for %s:",
-                         domain)
-            logger.exception(error)
+                         domain, exc_info=True)
 
     if not _save_and_restart(plugin, "enhanced"):
         return False
@@ -222,9 +219,8 @@ def _save_and_restart(plugin, title=None):
         plugin.save(title)
         plugin.restart()
         return True
-    except le_errors.Error as error:
-        logger.error("*** Plugin failed to save and restart server:")
-        logger.exception(error)
+    except le_errors.Error:
+        logger.error("*** Plugin failed to save and restart server:", exc_info=True)
         return False
 
 
@@ -232,9 +228,8 @@ def test_rollback(plugin, config, backup):
     """Tests the rollback checkpoints function"""
     try:
         plugin.rollback_checkpoints(1337)
-    except le_errors.Error as error:
-        logger.error("*** Plugin raised an exception during rollback:")
-        logger.exception(error)
+    except le_errors.Error:
+        logger.error("*** Plugin raised an exception during rollback:", exc_info=True)
         return False
 
     if _dirs_are_unequal(config, backup):
@@ -263,21 +258,21 @@ def _dirs_are_unequal(dir1, dir2):
             logger.error("The following files and directories are only "
                          "present in one directory")
             if dircmp.left_only:
-                logger.error(dircmp.left_only)
+                logger.error(str(dircmp.left_only))
             else:
-                logger.error(dircmp.right_only)
+                logger.error(str(dircmp.right_only))
             return True
         elif dircmp.common_funny or dircmp.funny_files:
             logger.error("The following files and directories could not be "
                          "compared:")
             if dircmp.common_funny:
-                logger.error(dircmp.common_funny)
+                logger.error(str(dircmp.common_funny))
             else:
-                logger.error(dircmp.funny_files)
+                logger.error(str(dircmp.funny_files))
             return True
         elif dircmp.diff_files:
             logger.error("The following files differ:")
-            logger.error(dircmp.diff_files)
+            logger.error(str(dircmp.diff_files))
             return True
 
         for subdir in dircmp.subdirs.itervalues():
@@ -354,9 +349,8 @@ def main():
                     success = test_authenticator(plugin, config, temp_dir)
                 if success and args.install:
                     success = test_installer(args, plugin, config, temp_dir)
-            except errors.Error as error:
-                logger.error("Tests on %s raised:", config)
-                logger.exception(error)
+            except errors.Error:
+                logger.error("Tests on %s raised:", config, exc_info=True)
                 success = False
 
             if success:
diff --git a/certbot-compatibility-test/certbot_compatibility_test/util.py b/certbot-compatibility-test/certbot_compatibility_test/util.py
index 4155944bd5b..6051bbc2eaa 100644
--- a/certbot-compatibility-test/certbot_compatibility_test/util.py
+++ b/certbot-compatibility-test/certbot_compatibility_test/util.py
@@ -26,12 +26,12 @@ def create_le_config(parent_dir):
     config = copy.deepcopy(constants.CLI_DEFAULTS)
 
     le_dir = os.path.join(parent_dir, "certbot")
-    config["config_dir"] = os.path.join(le_dir, "config")
-    config["work_dir"] = os.path.join(le_dir, "work")
-    config["logs_dir"] = os.path.join(le_dir, "logs_dir")
-    os.makedirs(config["config_dir"])
-    os.mkdir(config["work_dir"])
-    os.mkdir(config["logs_dir"])
+    os.mkdir(le_dir)
+    for dir_name in ("config", "logs", "work"):
+        full_path = os.path.join(le_dir, dir_name)
+        os.mkdir(full_path)
+        full_name = dir_name + "_dir"
+        config[full_name] = full_path
 
     config["domains"] = None
 
diff --git a/certbot-compatibility-test/certbot_compatibility_test/validator.py b/certbot-compatibility-test/certbot_compatibility_test/validator.py
index 791fe0da28a..fd2f95702ea 100644
--- a/certbot-compatibility-test/certbot_compatibility_test/validator.py
+++ b/certbot-compatibility-test/certbot_compatibility_test/validator.py
@@ -33,7 +33,7 @@ def certificate(self, cert, name, alt_host=None, port=443):
         try:
             presented_cert = crypto_util.probe_sni(name, host, port)
         except acme_errors.Error as error:
-            logger.exception(error)
+            logger.exception(str(error))
             return False
 
         return presented_cert.digest("sha256") == cert.digest("sha256")
@@ -86,8 +86,7 @@ def hsts(self, name):
             return False
 
         try:
-            _, max_age_value = max_age[0]
-            max_age_value = int(max_age_value)
+            max_age_value = int(max_age[0][1])
         except ValueError:
             logger.error("Server responded with invalid HSTS header field")
             return False
diff --git a/letshelp-certbot/letshelp_certbot/apache.py b/letshelp-certbot/letshelp_certbot/apache.py
index f77a6a1b03c..50f3c5ef64b 100755
--- a/letshelp-certbot/letshelp_certbot/apache.py
+++ b/letshelp-certbot/letshelp_certbot/apache.py
@@ -16,6 +16,8 @@
 
 import six
 
+from letshelp_certbot.magic_typing import List  # pylint: disable=unused-import, no-name-in-module
+
 _DESCRIPTION = """
 Let's Help is a simple script you can run to help out the Certbot
 project. Since Certbot will support automatically configuring HTTPS on
@@ -87,7 +89,8 @@ def copy_config(server_root, temp_dir):
     :rtype: `tuple` of `list` of `str`
 
     """
-    copied_files, copied_dirs = [], []
+    copied_files = [] # type: List[str]
+    copied_dirs = [] # type: List[str]
     dir_len = len(os.path.dirname(server_root))
 
     for config_path, config_dirs, config_files in os.walk(server_root):
diff --git a/letshelp-certbot/letshelp_certbot/apache_test.py b/letshelp-certbot/letshelp_certbot/apache_test.py
index e0656ae0596..a1115bc0695 100644
--- a/letshelp-certbot/letshelp_certbot/apache_test.py
+++ b/letshelp-certbot/letshelp_certbot/apache_test.py
@@ -203,13 +203,19 @@ def _test_main_common(self):
                         tempdir_path, "config.tar.gz"))
 
                     tempdir = tar.next()
-                    self.assertTrue(tempdir.isdir())
-                    self.assertEqual(tempdir.name, ".")
+                    if tempdir is None:
+                        self.fail("Invalid tarball!")  # pragma: no cover
+                    else:
+                        self.assertTrue(tempdir.isdir())
+                        self.assertEqual(tempdir.name, ".")
 
                     testdir = tar.next()
-                    self.assertTrue(testdir.isdir())
-                    self.assertEqual(os.path.basename(testdir.name),
-                                     testdir_basename)
+                    if testdir is None:
+                        self.fail("Invalid tarball!")  # pragma: no cover
+                    else:
+                        self.assertTrue(testdir.isdir())
+                        self.assertEqual(os.path.basename(testdir.name),
+                                         testdir_basename)
 
                     self.assertEqual(tar.next(), None)
 
diff --git a/letshelp-certbot/letshelp_certbot/magic_typing.py b/letshelp-certbot/letshelp_certbot/magic_typing.py
new file mode 100644
index 00000000000..471b8dfa95c
--- /dev/null
+++ b/letshelp-certbot/letshelp_certbot/magic_typing.py
@@ -0,0 +1,16 @@
+"""Shim class to not have to depend on typing module in prod."""
+import sys
+
+class TypingClass(object):
+    """Ignore import errors by getting anything"""
+    def __getattr__(self, name):
+        return None
+
+try:
+    # mypy doesn't respect modifying sys.modules
+    from typing import *  # pylint: disable=wildcard-import, unused-wildcard-import
+    # pylint: disable=unused-import
+    from typing import Collection, IO  # type: ignore
+    # pylint: enable=unused-import
+except ImportError:
+    sys.modules[__name__] = TypingClass()
diff --git a/letshelp-certbot/letshelp_certbot/magic_typing_test.py b/letshelp-certbot/letshelp_certbot/magic_typing_test.py
new file mode 100644
index 00000000000..200ca03b861
--- /dev/null
+++ b/letshelp-certbot/letshelp_certbot/magic_typing_test.py
@@ -0,0 +1,41 @@
+"""Tests for letshelp_certbot.magic_typing."""
+import sys
+import unittest
+
+import mock
+
+
+class MagicTypingTest(unittest.TestCase):
+    """Tests for letshelp_certbot.magic_typing."""
+    def test_import_success(self):
+        try:
+            import typing as temp_typing
+        except ImportError: # pragma: no cover
+            temp_typing = None # pragma: no cover
+        typing_class_mock = mock.MagicMock()
+        text_mock = mock.MagicMock()
+        typing_class_mock.Text = text_mock
+        sys.modules['typing'] = typing_class_mock
+        if 'letshelp_certbot.magic_typing' in sys.modules:
+            del sys.modules['letshelp_certbot.magic_typing'] # pragma: no cover
+        from letshelp_certbot.magic_typing import Text # pylint: disable=no-name-in-module
+        self.assertEqual(Text, text_mock)
+        del sys.modules['letshelp_certbot.magic_typing']
+        sys.modules['typing'] = temp_typing
+
+    def test_import_failure(self):
+        try:
+            import typing as temp_typing
+        except ImportError: # pragma: no cover
+            temp_typing = None # pragma: no cover
+        sys.modules['typing'] = None
+        if 'letshelp_certbot.magic_typing' in sys.modules:
+            del sys.modules['letshelp_certbot.magic_typing'] # pragma: no cover
+        from letshelp_certbot.magic_typing import Text # pylint: disable=no-name-in-module
+        self.assertTrue(Text is None)
+        del sys.modules['letshelp_certbot.magic_typing']
+        sys.modules['typing'] = temp_typing
+
+
+if __name__ == '__main__':
+    unittest.main()  # pragma: no cover
diff --git a/mypy.ini b/mypy.ini
index f0c99e65f00..188ed031fc7 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -1,48 +1,10 @@
 [mypy]
-python_version = 2.7
-ignore_missing_imports = True
-
-[mypy-acme.*]
 check_untyped_defs = True
+ignore_missing_imports = True
+python_version = 2.7
 
 [mypy-acme.magic_typing_test]
 ignore_errors = True
 
-[mypy-certbot.*]
-check_untyped_defs = True
-
-[mypy-certbot_apache.*]
-check_untyped_defs = True
-
-[mypy-certbot_dns_cloudflare.*]
-check_untyped_defs = True
-
-[mypy-certbot_dns_cloudxns.*]
-check_untyped_defs = True
-
-[mypy-certbot_dns_digitalocean.*]
-check_untyped_defs = True
-
-[mypy-certbot_dns_dnsimple.*]
-check_untyped_defs = True
-
-[mypy-certbot_dns_dnsmadeeasy.*]
-check_untyped_defs = True
-
-[mypy-certbot_dns_google.*]
-check_untyped_defs = True
-
-[mypy-certbot_dns_luadns.*]
-check_untyped_defs = True
-
-[mypy-certbot_dns_nsone.*]
-check_untyped_defs = True
-
-[mypy-certbot_dns_rfc2136.*]
-check_untyped_defs = True
-
-[mypy-certbot_dns_route53.*]
-check_untyped_defs = True
-
-[mypy-certbot_nginx.*]
-check_untyped_defs = True
+[mypy-letshelp_certbot.magic_typing_test]
+ignore_errors = True
diff --git a/tests/lock_test.py b/tests/lock_test.py
index 4bb2865b4ae..b01cc5d5806 100644
--- a/tests/lock_test.py
+++ b/tests/lock_test.py
@@ -198,7 +198,7 @@ def report_failure(err_msg, out, err):
     :param str err: stderr output
 
     """
-    logger.fatal(err_msg)
+    logger.critical(err_msg)
     log_output(logging.INFO, out, err)
     sys.exit(err_msg)
 

This finishes getting mypy to pass with `--check-untyped-defs` for everything in this repo. To make it easy to distribute, `letshelp-certbot` has no dependencies outside of the standard library so rather than adding some so rather than add some, I just copied `acme.magic_typing` to that package as well.