diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fa395c57..e5a89e66 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ ci: autoupdate_schedule: monthly repos: - repo: https://github.com/asottile/pyupgrade - rev: v2.32.0 + rev: v2.32.1 hooks: - id: pyupgrade args: ["--py36-plus"] diff --git a/requirements/dev.txt b/requirements/dev.txt index 36999074..2cdefc96 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -10,19 +10,19 @@ -r typing.txt cfgv==3.3.1 # via pre-commit -click==8.1.2 +click==8.1.3 # via # pip-compile-multi # pip-tools distlib==0.3.4 # via virtualenv -filelock==3.6.0 +filelock==3.7.0 # via # tox # virtualenv greenlet==1.1.2 ; python_version < "3.11" # via -r requirements/tests.in -identify==2.5.0 +identify==2.5.1 # via pre-commit nodeenv==1.6.0 # via pre-commit @@ -30,11 +30,11 @@ pep517==0.12.0 # via pip-tools pip-compile-multi==2.4.5 # via -r requirements/dev.in -pip-tools==6.6.0 +pip-tools==6.6.1 # via pip-compile-multi platformdirs==2.5.2 # via virtualenv -pre-commit==2.18.1 +pre-commit==2.19.0 # via -r requirements/dev.in pyyaml==6.0 # via pre-commit diff --git a/requirements/docs.txt b/requirements/docs.txt index 963d81e9..f94d30b8 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -9,7 +9,7 @@ alabaster==0.7.12 # via sphinx babel==2.10.1 # via sphinx -certifi==2021.10.8 +certifi==2022.5.18.1 # via requests charset-normalizer==2.0.12 # via requests @@ -21,7 +21,7 @@ idna==3.3 # via requests imagesize==1.3.0 # via sphinx -jinja2==3.1.1 +jinja2==3.1.2 # via sphinx markupsafe==2.1.1 # via jinja2 @@ -35,7 +35,7 @@ pygments==2.12.0 # via # sphinx # sphinx-tabs -pyparsing==3.0.8 +pyparsing==3.0.9 # via packaging pytz==2022.1 # via babel diff --git a/requirements/tests.txt b/requirements/tests.txt index 7833cabd..7a3f89d7 100644 --- a/requirements/tests.txt +++ b/requirements/tests.txt @@ -5,7 +5,7 @@ # # pip-compile-multi # -asgiref==3.5.0 +asgiref==3.5.2 # via -r requirements/tests.in attrs==21.4.0 # via pytest @@ -21,7 +21,7 @@ pluggy==1.0.0 # via pytest py==1.11.0 # via pytest -pyparsing==3.0.8 +pyparsing==3.0.9 # via packaging pytest==7.1.2 # via -r requirements/tests.in diff --git a/requirements/typing.txt b/requirements/typing.txt index f1aa5284..bf5e2a39 100644 --- a/requirements/typing.txt +++ b/requirements/typing.txt @@ -7,7 +7,7 @@ # cffi==1.15.0 # via cryptography -cryptography==37.0.1 +cryptography==37.0.2 # via -r requirements/typing.in mypy==0.950 # via -r requirements/typing.in @@ -21,7 +21,7 @@ types-contextvars==2.4.5 # via -r requirements/typing.in types-dataclasses==0.6.5 # via -r requirements/typing.in -types-setuptools==57.4.14 +types-setuptools==57.4.15 # via -r requirements/typing.in typing-extensions==4.2.0 # via mypy 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"])