From 1232d698600e11dcb83bb5dc349ca785eae02d2f Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 23 May 2022 09:46:20 -0700 Subject: [PATCH] inline conditional imports for cli behaviors --- setup.cfg | 3 +++ src/flask/cli.py | 53 ++++++++++++++++++++--------------------------- tests/test_cli.py | 19 +++++++++++++---- 3 files changed, 40 insertions(+), 35 deletions(-) diff --git a/setup.cfg b/setup.cfg index 31a590a4..597eece1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -116,3 +116,6 @@ ignore_missing_imports = True [mypy-cryptography.*] ignore_missing_imports = True + +[mypy-importlib_metadata] +ignore_missing_imports = True diff --git a/src/flask/cli.py b/src/flask/cli.py index efcc0f99..77c1e25a 100644 --- a/src/flask/cli.py +++ b/src/flask/cli.py @@ -9,8 +9,6 @@ from functools import update_wrapper from operator import attrgetter from threading import Lock from threading import Thread -from typing import Any -from typing import TYPE_CHECKING import click from werkzeug.utils import import_string @@ -20,31 +18,6 @@ from .helpers import get_debug_flag from .helpers import get_env from .helpers import get_load_dotenv -try: - import dotenv -except ImportError: - dotenv = None - -try: - import ssl -except ImportError: - ssl = None # type: ignore - -if sys.version_info >= (3, 10): - from importlib import metadata -else: - # Use a backport on Python < 3.10. - # - # We technically have importlib.metadata on 3.8+, - # but the API changed in 3.10, so use the backport - # for consistency. - if TYPE_CHECKING: - metadata: Any - else: - # we do this to avoid a version dependent mypy error - # because importlib_metadata is not installed in python3.10+ - import importlib_metadata as metadata - class NoAppException(click.UsageError): """Raised if an application cannot be found or loaded.""" @@ -520,6 +493,14 @@ class FlaskGroup(AppGroup): if self._loaded_plugin_commands: return + if sys.version_info >= (3, 10): + from importlib import metadata + else: + # Use a backport on Python < 3.10. We technically have + # importlib.metadata on 3.8+, but the API changed in 3.10, + # so use the backport for consistency. + import importlib_metadata as metadata + for ep in metadata.entry_points(group="flask.commands"): self.add_command(ep.load(), ep.name) @@ -615,7 +596,9 @@ def load_dotenv(path=None): .. versionadded:: 1.0 """ - if dotenv is None: + try: + import dotenv + except ImportError: if path or os.path.isfile(".env") or os.path.isfile(".flaskenv"): click.secho( " * Tip: There are .env or .flaskenv files present." @@ -691,12 +674,14 @@ class CertParamType(click.ParamType): self.path_type = click.Path(exists=True, dir_okay=False, resolve_path=True) def convert(self, value, param, ctx): - if ssl is None: + try: + import ssl + except ImportError: raise click.BadParameter( 'Using "--cert" requires Python to be compiled with SSL support.', ctx, param, - ) + ) from None try: return self.path_type(value, param, ctx) @@ -729,7 +714,13 @@ def _validate_key(ctx, param, value): """ cert = ctx.params.get("cert") is_adhoc = cert == "adhoc" - is_context = ssl and isinstance(cert, ssl.SSLContext) + + try: + import ssl + except ImportError: + is_context = False + else: + is_context = isinstance(cert, ssl.SSLContext) if value is not None: if is_adhoc: diff --git a/tests/test_cli.py b/tests/test_cli.py index 6e8b57f0..c9dd5ade 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -18,7 +18,6 @@ from flask import current_app from flask import Flask from flask.cli import AppGroup from flask.cli import DispatchingApp -from flask.cli import dotenv from flask.cli import find_best_app from flask.cli import FlaskGroup from flask.cli import get_version @@ -492,7 +491,18 @@ class TestRoutes: assert "No routes were registered." in result.output -need_dotenv = pytest.mark.skipif(dotenv is None, reason="dotenv is not installed") +def dotenv_not_available(): + try: + import dotenv # noqa: F401 + except ImportError: + return True + + return False + + +need_dotenv = pytest.mark.skipif( + dotenv_not_available(), reason="dotenv is not installed" +) @need_dotenv @@ -530,7 +540,7 @@ def test_dotenv_path(monkeypatch): def test_dotenv_optional(monkeypatch): - monkeypatch.setattr("flask.cli.dotenv", None) + monkeypatch.setitem(sys.modules, "dotenv", None) monkeypatch.chdir(test_path) load_dotenv() assert "FOO" not in os.environ @@ -602,7 +612,8 @@ def test_run_cert_import(monkeypatch): def test_run_cert_no_ssl(monkeypatch): - monkeypatch.setattr("flask.cli.ssl", None) + monkeypatch.setitem(sys.modules, "ssl", None) + with pytest.raises(click.BadParameter): run_command.make_context("run", ["--cert", "not_here"])