drop support for Python 3.8

This commit is contained in:
David Lord 2024-10-31 12:28:46 -07:00
parent e8b91cd38a
commit 1d610e44b3
No known key found for this signature in database
GPG key ID: 43368A7AA8CC5926
12 changed files with 25 additions and 42 deletions

View file

@ -20,7 +20,6 @@ jobs:
- {python: '3.11'} - {python: '3.11'}
- {python: '3.10'} - {python: '3.10'}
- {python: '3.9'} - {python: '3.9'}
- {python: '3.8'}
- {name: PyPy, python: 'pypy-3.10', tox: pypy310} - {name: PyPy, python: 'pypy-3.10', tox: pypy310}
- {name: Minimum Versions, python: '3.12', tox: py-min} - {name: Minimum Versions, python: '3.12', tox: py-min}
- {name: Development Versions, python: '3.9', tox: py-dev} - {name: Development Versions, python: '3.9', tox: py-dev}

View file

@ -3,6 +3,7 @@ Version 3.1.0
Unreleased Unreleased
- Drop support for Python 3.8. :pr:`5623`
- Provide a configuration option to control automatic option - Provide a configuration option to control automatic option
responses. :pr:`5496` responses. :pr:`5496`
- ``Flask.open_resource``/``open_instance_resource`` and - ``Flask.open_resource``/``open_instance_resource`` and

View file

@ -23,12 +23,6 @@ method in views that inherit from the :class:`flask.views.View` class, as
well as all the HTTP method handlers in views that inherit from the well as all the HTTP method handlers in views that inherit from the
:class:`flask.views.MethodView` class. :class:`flask.views.MethodView` class.
.. admonition:: Using ``async`` on Windows on Python 3.8
Python 3.8 has a bug related to asyncio on Windows. If you encounter
something like ``ValueError: set_wakeup_fd only works in main thread``,
please upgrade to Python 3.9.
.. admonition:: Using ``async`` with greenlet .. admonition:: Using ``async`` with greenlet
When using gevent or eventlet to serve an application or patch the When using gevent or eventlet to serve an application or patch the

View file

@ -294,7 +294,8 @@ ecosystem remain consistent and compatible.
indicate minimum compatibility support. For example, indicate minimum compatibility support. For example,
``sqlalchemy>=1.4``. ``sqlalchemy>=1.4``.
9. Indicate the versions of Python supported using ``python_requires=">=version"``. 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. Flask itself supports Python >=3.9 as of October 2024, and this will update
over time.
.. _PyPI: https://pypi.org/search/?c=Framework+%3A%3A+Flask .. _PyPI: https://pypi.org/search/?c=Framework+%3A%3A+Flask
.. _Discord Chat: https://discord.gg/pallets .. _Discord Chat: https://discord.gg/pallets

View file

@ -5,7 +5,7 @@ Installation
Python Version Python Version
-------------- --------------
We recommend using the latest version of Python. Flask supports Python 3.8 and newer. We recommend using the latest version of Python. Flask supports Python 3.9 and newer.
Dependencies Dependencies

View file

@ -19,7 +19,7 @@ classifiers = [
"Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Application Frameworks",
"Typing :: Typed", "Typing :: Typed",
] ]
requires-python = ">=3.8" requires-python = ">=3.9"
dependencies = [ dependencies = [
"Werkzeug>=3.0.0", "Werkzeug>=3.0.0",
"Jinja2>=3.1.2", "Jinja2>=3.1.2",
@ -78,7 +78,7 @@ source = ["flask", "tests"]
source = ["src", "*/site-packages"] source = ["src", "*/site-packages"]
[tool.mypy] [tool.mypy]
python_version = "3.8" python_version = "3.9"
files = ["src/flask", "tests/typing"] files = ["src/flask", "tests/typing"]
show_error_codes = true show_error_codes = true
pretty = true pretty = true
@ -94,7 +94,7 @@ module = [
ignore_missing_imports = true ignore_missing_imports = true
[tool.pyright] [tool.pyright]
pythonVersion = "3.8" pythonVersion = "3.9"
include = ["src/flask", "tests/typing"] include = ["src/flask", "tests/typing"]
typeCheckingMode = "basic" typeCheckingMode = "basic"

View file

@ -1245,7 +1245,7 @@ class Flask(App):
# extend existing headers with provided headers # extend existing headers with provided headers
if headers: if headers:
rv.headers.update(headers) # type: ignore[arg-type] rv.headers.update(headers)
return rv return rv

View file

@ -150,13 +150,13 @@ class Config(dict): # type: ignore[type-arg]
.. versionadded:: 2.1 .. versionadded:: 2.1
""" """
prefix = f"{prefix}_" prefix = f"{prefix}_"
len_prefix = len(prefix)
for key in sorted(os.environ): for key in sorted(os.environ):
if not key.startswith(prefix): if not key.startswith(prefix):
continue continue
value = os.environ[key] value = os.environ[key]
key = key.removeprefix(prefix)
try: try:
value = loads(value) value = loads(value)
@ -164,9 +164,6 @@ class Config(dict): # type: ignore[type-arg]
# Keep the value as a string if loading failed. # Keep the value as a string if loading failed.
pass pass
# Change to key.removeprefix(prefix) on Python >= 3.9.
key = key[len_prefix:]
if "__" not in key: if "__" not in key:
# A non-nested key, set directly. # A non-nested key, set directly.
self[key] = value self[key] = value

View file

@ -5,7 +5,7 @@ import os
import sys import sys
import typing as t import typing as t
from datetime import datetime from datetime import datetime
from functools import lru_cache from functools import cache
from functools import update_wrapper from functools import update_wrapper
import werkzeug.utils import werkzeug.utils
@ -623,7 +623,7 @@ def get_root_path(import_name: str) -> str:
return os.path.dirname(os.path.abspath(filepath)) # type: ignore[no-any-return] return os.path.dirname(os.path.abspath(filepath)) # type: ignore[no-any-return]
@lru_cache(maxsize=None) @cache
def _split_blueprint_path(name: str) -> list[str]: def _split_blueprint_path(name: str) -> list[str]:
out: list[str] = [name] out: list[str] = [name]

View file

@ -706,15 +706,6 @@ def _endpoint_from_view_func(view_func: ft.RouteCallable) -> str:
return view_func.__name__ return view_func.__name__
def _path_is_relative_to(path: pathlib.PurePath, base: str) -> bool:
# Path.is_relative_to doesn't exist until Python 3.9
try:
path.relative_to(base)
return True
except ValueError:
return False
def _find_package_path(import_name: str) -> str: def _find_package_path(import_name: str) -> str:
"""Find the path that contains the package or module.""" """Find the path that contains the package or module."""
root_mod_name, _, _ = import_name.partition(".") root_mod_name, _, _ = import_name.partition(".")
@ -745,7 +736,7 @@ def _find_package_path(import_name: str) -> str:
search_location = next( search_location = next(
location location
for location in root_spec.submodule_search_locations for location in root_spec.submodule_search_locations
if _path_is_relative_to(package_path, location) if package_path.is_relative_to(location)
) )
else: else:
# Pick the first path. # Pick the first path.
@ -777,7 +768,7 @@ def find_package(import_name: str) -> tuple[str | None, str]:
py_prefix = os.path.abspath(sys.prefix) py_prefix = os.path.abspath(sys.prefix)
# installed to the system # installed to the system
if _path_is_relative_to(pathlib.PurePath(package_path), py_prefix): if pathlib.PurePath(package_path).is_relative_to(py_prefix):
return py_prefix, package_path return py_prefix, package_path
site_parent, site_folder = os.path.split(package_path) site_parent, site_folder = os.path.split(package_path)

View file

@ -12,7 +12,7 @@ ResponseValue = t.Union[
"Response", "Response",
str, str,
bytes, bytes,
t.List[t.Any], list[t.Any],
# Only dict is actually accepted, but Mapping allows for TypedDict. # Only dict is actually accepted, but Mapping allows for TypedDict.
t.Mapping[str, t.Any], t.Mapping[str, t.Any],
t.Iterator[str], t.Iterator[str],
@ -21,21 +21,21 @@ ResponseValue = t.Union[
# the possible types for an individual HTTP header # the possible types for an individual HTTP header
# This should be a Union, but mypy doesn't pass unless it's a TypeVar. # This should be a Union, but mypy doesn't pass unless it's a TypeVar.
HeaderValue = t.Union[str, t.List[str], t.Tuple[str, ...]] HeaderValue = t.Union[str, list[str], tuple[str, ...]]
# the possible types for HTTP headers # the possible types for HTTP headers
HeadersValue = t.Union[ HeadersValue = t.Union[
"Headers", "Headers",
t.Mapping[str, HeaderValue], t.Mapping[str, HeaderValue],
t.Sequence[t.Tuple[str, HeaderValue]], t.Sequence[tuple[str, HeaderValue]],
] ]
# The possible types returned by a route function. # The possible types returned by a route function.
ResponseReturnValue = t.Union[ ResponseReturnValue = t.Union[
ResponseValue, ResponseValue,
t.Tuple[ResponseValue, HeadersValue], tuple[ResponseValue, HeadersValue],
t.Tuple[ResponseValue, int], tuple[ResponseValue, int],
t.Tuple[ResponseValue, int, HeadersValue], tuple[ResponseValue, int, HeadersValue],
"WSGIApplication", "WSGIApplication",
] ]
@ -56,21 +56,21 @@ BeforeRequestCallable = t.Union[
t.Callable[[], t.Optional[ResponseReturnValue]], t.Callable[[], t.Optional[ResponseReturnValue]],
t.Callable[[], t.Awaitable[t.Optional[ResponseReturnValue]]], t.Callable[[], t.Awaitable[t.Optional[ResponseReturnValue]]],
] ]
ShellContextProcessorCallable = t.Callable[[], t.Dict[str, t.Any]] ShellContextProcessorCallable = t.Callable[[], dict[str, t.Any]]
TeardownCallable = t.Union[ TeardownCallable = t.Union[
t.Callable[[t.Optional[BaseException]], None], t.Callable[[t.Optional[BaseException]], None],
t.Callable[[t.Optional[BaseException]], t.Awaitable[None]], t.Callable[[t.Optional[BaseException]], t.Awaitable[None]],
] ]
TemplateContextProcessorCallable = t.Union[ TemplateContextProcessorCallable = t.Union[
t.Callable[[], t.Dict[str, t.Any]], t.Callable[[], dict[str, t.Any]],
t.Callable[[], t.Awaitable[t.Dict[str, t.Any]]], t.Callable[[], t.Awaitable[dict[str, t.Any]]],
] ]
TemplateFilterCallable = t.Callable[..., t.Any] TemplateFilterCallable = t.Callable[..., t.Any]
TemplateGlobalCallable = t.Callable[..., t.Any] TemplateGlobalCallable = t.Callable[..., t.Any]
TemplateTestCallable = t.Callable[..., bool] TemplateTestCallable = t.Callable[..., bool]
URLDefaultCallable = t.Callable[[str, t.Dict[str, t.Any]], None] URLDefaultCallable = t.Callable[[str, dict[str, t.Any]], None]
URLValuePreprocessorCallable = t.Callable[ URLValuePreprocessorCallable = t.Callable[
[t.Optional[str], t.Optional[t.Dict[str, t.Any]]], None [t.Optional[str], t.Optional[dict[str, t.Any]]], None
] ]
# This should take Exception, but that either breaks typing the argument # This should take Exception, but that either breaks typing the argument

View file

@ -1,6 +1,6 @@
[tox] [tox]
envlist = envlist =
py3{13,12,11,10,9,8} py3{13,12,11,10,9}
pypy310 pypy310
py312-min py312-min
py39-dev py39-dev