inline conditional imports for cli behaviors

This commit is contained in:
David Lord 2022-05-23 09:46:20 -07:00
parent e322f32e94
commit 1232d69860
No known key found for this signature in database
GPG Key ID: 7A1C87E3F5BC42A8
3 changed files with 40 additions and 35 deletions

View File

@ -116,3 +116,6 @@ ignore_missing_imports = True
[mypy-cryptography.*] [mypy-cryptography.*]
ignore_missing_imports = True ignore_missing_imports = True
[mypy-importlib_metadata]
ignore_missing_imports = True

View File

@ -9,8 +9,6 @@ from functools import update_wrapper
from operator import attrgetter from operator import attrgetter
from threading import Lock from threading import Lock
from threading import Thread from threading import Thread
from typing import Any
from typing import TYPE_CHECKING
import click import click
from werkzeug.utils import import_string 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_env
from .helpers import get_load_dotenv 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): class NoAppException(click.UsageError):
"""Raised if an application cannot be found or loaded.""" """Raised if an application cannot be found or loaded."""
@ -520,6 +493,14 @@ class FlaskGroup(AppGroup):
if self._loaded_plugin_commands: if self._loaded_plugin_commands:
return 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"): for ep in metadata.entry_points(group="flask.commands"):
self.add_command(ep.load(), ep.name) self.add_command(ep.load(), ep.name)
@ -615,7 +596,9 @@ def load_dotenv(path=None):
.. versionadded:: 1.0 .. versionadded:: 1.0
""" """
if dotenv is None: try:
import dotenv
except ImportError:
if path or os.path.isfile(".env") or os.path.isfile(".flaskenv"): if path or os.path.isfile(".env") or os.path.isfile(".flaskenv"):
click.secho( click.secho(
" * Tip: There are .env or .flaskenv files present." " * 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) self.path_type = click.Path(exists=True, dir_okay=False, resolve_path=True)
def convert(self, value, param, ctx): def convert(self, value, param, ctx):
if ssl is None: try:
import ssl
except ImportError:
raise click.BadParameter( raise click.BadParameter(
'Using "--cert" requires Python to be compiled with SSL support.', 'Using "--cert" requires Python to be compiled with SSL support.',
ctx, ctx,
param, param,
) ) from None
try: try:
return self.path_type(value, param, ctx) return self.path_type(value, param, ctx)
@ -729,7 +714,13 @@ def _validate_key(ctx, param, value):
""" """
cert = ctx.params.get("cert") cert = ctx.params.get("cert")
is_adhoc = cert == "adhoc" 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 value is not None:
if is_adhoc: if is_adhoc:

View File

@ -18,7 +18,6 @@ from flask import current_app
from flask import Flask from flask import Flask
from flask.cli import AppGroup from flask.cli import AppGroup
from flask.cli import DispatchingApp from flask.cli import DispatchingApp
from flask.cli import dotenv
from flask.cli import find_best_app from flask.cli import find_best_app
from flask.cli import FlaskGroup from flask.cli import FlaskGroup
from flask.cli import get_version from flask.cli import get_version
@ -492,7 +491,18 @@ class TestRoutes:
assert "No routes were registered." in result.output 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 @need_dotenv
@ -530,7 +540,7 @@ def test_dotenv_path(monkeypatch):
def test_dotenv_optional(monkeypatch): def test_dotenv_optional(monkeypatch):
monkeypatch.setattr("flask.cli.dotenv", None) monkeypatch.setitem(sys.modules, "dotenv", None)
monkeypatch.chdir(test_path) monkeypatch.chdir(test_path)
load_dotenv() load_dotenv()
assert "FOO" not in os.environ assert "FOO" not in os.environ
@ -602,7 +612,8 @@ def test_run_cert_import(monkeypatch):
def test_run_cert_no_ssl(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): with pytest.raises(click.BadParameter):
run_command.make_context("run", ["--cert", "not_here"]) run_command.make_context("run", ["--cert", "not_here"])