diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 92211a55..79c382fe 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -31,10 +31,9 @@ jobs: - {name: '3.10', python: '3.10', os: ubuntu-latest, tox: py310} - {name: '3.9', python: '3.9', os: ubuntu-latest, tox: py39} - {name: '3.8', python: '3.8', os: ubuntu-latest, tox: py38} - - {name: '3.7', python: '3.7', os: ubuntu-latest, tox: py37} - {name: 'PyPy', python: 'pypy-3.9', os: ubuntu-latest, tox: pypy39} - {name: 'Minimum Versions', python: '3.11', os: ubuntu-latest, tox: py311-min} - - {name: 'Development Versions', python: '3.7', os: ubuntu-latest, tox: py37-dev} + - {name: 'Development Versions', python: '3.8', os: ubuntu-latest, tox: py38-dev} - {name: Typing, python: '3.11', os: ubuntu-latest, tox: typing} steps: - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2122651e..6615cc2c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: rev: v3.3.1 hooks: - id: pyupgrade - args: ["--py37-plus"] + args: ["--py38-plus"] - repo: https://github.com/asottile/reorder_python_imports rev: v3.9.0 hooks: diff --git a/CHANGES.rst b/CHANGES.rst index 0589713d..f59f5bb6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,7 @@ Version 2.3.0 Unreleased +- Drop support for Python 3.7. :pr:`5072` - Remove previously deprecated code. :pr:`4995` - The ``push`` and ``pop`` methods of the deprecated ``_app_ctx_stack`` and diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 4ddb6da0..c9dee5ff 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -293,9 +293,8 @@ ecosystem remain consistent and compatible. any particular version scheme, but should use lower bounds to indicate minimum compatibility support. For example, ``sqlalchemy>=1.4``. -9. Indicate the versions of Python supported using - ``python_requires=">=version"``. Flask itself supports Python >=3.7 - as of December 2021, but this will update over time. +9. Indicate the versions of Python supported using ``python_requires=">=version"``. + Flask itself supports Python >=3.8 as of April 2023, but this will update over time. .. _PyPI: https://pypi.org/search/?c=Framework+%3A%3A+Flask .. _Discord Chat: https://discord.gg/pallets diff --git a/docs/installation.rst b/docs/installation.rst index 0c9b1a47..aeb00ce1 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -5,8 +5,7 @@ Installation Python Version -------------- -We recommend using the latest version of Python. Flask supports Python -3.7 and newer. +We recommend using the latest version of Python. Flask supports Python 3.8 and newer. Dependencies diff --git a/examples/celery/pyproject.toml b/examples/celery/pyproject.toml index 88ba6b96..e480aebc 100644 --- a/examples/celery/pyproject.toml +++ b/examples/celery/pyproject.toml @@ -3,7 +3,7 @@ name = "flask-example-celery" version = "1.0.0" description = "Example Flask application with Celery background tasks." readme = "README.md" -requires-python = ">=3.7" +requires-python = ">=3.8" dependencies = ["flask>=2.2.2", "celery[redis]>=5.2.7"] [build-system] diff --git a/pyproject.toml b/pyproject.toml index 7891ea76..544e1696 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ classifiers = [ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", "Topic :: Software Development :: Libraries :: Application Frameworks", ] -requires-python = ">=3.7" +requires-python = ">=3.8" dependencies = [ "Werkzeug>=2.2.2", "Jinja2>=3.0", @@ -67,7 +67,7 @@ source = ["flask", "tests"] source = ["src", "*/site-packages"] [tool.mypy] -python_version = "3.7" +python_version = "3.8" files = ["src/flask"] show_error_codes = true pretty = true diff --git a/requirements/tests.in b/requirements/tests.in index 25f6b5bd..f4b3dad8 100644 --- a/requirements/tests.in +++ b/requirements/tests.in @@ -1,4 +1,4 @@ pytest asgiref greenlet ; python_version < "3.11" -python-dotenv>=1; python_version >= "3.8" +python-dotenv diff --git a/requirements/tests.txt b/requirements/tests.txt index 15bf2880..29044fb5 100644 --- a/requirements/tests.txt +++ b/requirements/tests.txt @@ -1,4 +1,4 @@ -# SHA1:3c8dde35aba20388b22430b17974af8ef8205b4f +# SHA1:42d37aff22e2f1fc447e20d483e13d6d4e066b10 # # This file is autogenerated by pip-compile-multi # To update, run: @@ -15,5 +15,5 @@ pluggy==1.0.0 # via pytest pytest==7.3.1 # via -r requirements/tests.in -python-dotenv==1.0.0 ; python_version >= "3.8" +python-dotenv==1.0.0 # via -r requirements/tests.in diff --git a/src/flask/app.py b/src/flask/app.py index 4e4c153f..3b6b38d8 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -1,7 +1,5 @@ from __future__ import annotations -import functools -import inspect import logging import os import sys @@ -9,6 +7,7 @@ import typing as t import weakref from collections.abc import Iterator as _abc_Iterator from datetime import timedelta +from inspect import iscoroutinefunction from itertools import chain from types import TracebackType from urllib.parse import quote as _url_quote @@ -70,7 +69,6 @@ from .wrappers import Request from .wrappers import Response if t.TYPE_CHECKING: # pragma: no cover - import typing_extensions as te from .blueprints import Blueprint from .testing import FlaskClient from .testing import FlaskCliRunner @@ -83,19 +81,6 @@ T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallab T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable) T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable) -if sys.version_info >= (3, 8): - iscoroutinefunction = inspect.iscoroutinefunction -else: - - def iscoroutinefunction(func: t.Any) -> bool: - while inspect.ismethod(func): - func = func.__func__ - - while isinstance(func, functools.partial): - func = func.func - - return inspect.iscoroutinefunction(func) - def _make_timedelta(value: timedelta | int | None) -> timedelta | None: if value is None or isinstance(value, timedelta): @@ -1430,7 +1415,7 @@ class Flask(Scaffold): f"Exception on {request.path} [{request.method}]", exc_info=exc_info ) - def raise_routing_exception(self, request: Request) -> te.NoReturn: + def raise_routing_exception(self, request: Request) -> t.NoReturn: """Intercept routing exceptions and possibly do something else. In debug mode, intercept a routing redirect and replace it with diff --git a/src/flask/helpers.py b/src/flask/helpers.py index cc77ada9..61a0f818 100644 --- a/src/flask/helpers.py +++ b/src/flask/helpers.py @@ -25,7 +25,6 @@ from .signals import message_flashed if t.TYPE_CHECKING: # pragma: no cover from werkzeug.wrappers import Response as BaseResponse from .wrappers import Response - import typing_extensions as te def get_debug_flag() -> bool: @@ -257,7 +256,7 @@ def redirect( return _wz_redirect(location, code=code, Response=Response) -def abort(code: int | BaseResponse, *args: t.Any, **kwargs: t.Any) -> te.NoReturn: +def abort(code: int | BaseResponse, *args: t.Any, **kwargs: t.Any) -> t.NoReturn: """Raise an :exc:`~werkzeug.exceptions.HTTPException` for the given status code. diff --git a/src/flask/sessions.py b/src/flask/sessions.py index 037cb0bd..1334184e 100644 --- a/src/flask/sessions.py +++ b/src/flask/sessions.py @@ -13,7 +13,6 @@ from werkzeug.datastructures import CallbackDict from .json.tag import TaggedJSONSerializer if t.TYPE_CHECKING: # pragma: no cover - import typing_extensions as te from .app import Flask from .wrappers import Request, Response @@ -94,7 +93,7 @@ class NullSession(SecureCookieSession): but fail on setting. """ - def _fail(self, *args: t.Any, **kwargs: t.Any) -> te.NoReturn: + def _fail(self, *args: t.Any, **kwargs: t.Any) -> t.NoReturn: raise RuntimeError( "The session is unavailable because no secret " "key was set. Set the secret_key on the " diff --git a/tests/typing/typing_route.py b/tests/typing/typing_route.py index 566280c0..bbd044ae 100644 --- a/tests/typing/typing_route.py +++ b/tests/typing/typing_route.py @@ -3,8 +3,6 @@ from __future__ import annotations import typing as t from http import HTTPStatus -import typing_extensions as te - from flask import Flask from flask import jsonify from flask import stream_template @@ -40,7 +38,7 @@ def hello_json_list() -> t.List[t.Any]: return [{"message": "Hello"}, {"message": "World"}] -class StatusJSON(te.TypedDict): +class StatusJSON(t.TypedDict): status: str diff --git a/tox.ini b/tox.ini index 7f799390..6ade371c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,9 @@ [tox] envlist = - py3{12,11,10,9,8,7} + py3{12,11,10,9,8} pypy39 py311-min - py37-dev + py38-dev style typing docs