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.10'}
- {python: '3.9'}
- {python: '3.8'}
- {name: PyPy, python: 'pypy-3.10', tox: pypy310}
- {name: Minimum Versions, python: '3.12', tox: py-min}
- {name: Development Versions, python: '3.9', tox: py-dev}

View file

@ -3,6 +3,7 @@ Version 3.1.0
Unreleased
- Drop support for Python 3.8. :pr:`5623`
- Provide a configuration option to control automatic option
responses. :pr:`5496`
- ``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
: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
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,
``sqlalchemy>=1.4``.
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
.. _Discord Chat: https://discord.gg/pallets

View file

@ -5,7 +5,7 @@ Installation
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

View file

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

View file

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

View file

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

View file

@ -5,7 +5,7 @@ import os
import sys
import typing as t
from datetime import datetime
from functools import lru_cache
from functools import cache
from functools import update_wrapper
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]
@lru_cache(maxsize=None)
@cache
def _split_blueprint_path(name: str) -> list[str]:
out: list[str] = [name]

View file

@ -706,15 +706,6 @@ def _endpoint_from_view_func(view_func: ft.RouteCallable) -> str:
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:
"""Find the path that contains the package or module."""
root_mod_name, _, _ = import_name.partition(".")
@ -745,7 +736,7 @@ def _find_package_path(import_name: str) -> str:
search_location = next(
location
for location in root_spec.submodule_search_locations
if _path_is_relative_to(package_path, location)
if package_path.is_relative_to(location)
)
else:
# 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)
# 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
site_parent, site_folder = os.path.split(package_path)

View file

@ -12,7 +12,7 @@ ResponseValue = t.Union[
"Response",
str,
bytes,
t.List[t.Any],
list[t.Any],
# Only dict is actually accepted, but Mapping allows for TypedDict.
t.Mapping[str, t.Any],
t.Iterator[str],
@ -21,21 +21,21 @@ ResponseValue = t.Union[
# the possible types for an individual HTTP header
# 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
HeadersValue = t.Union[
"Headers",
t.Mapping[str, HeaderValue],
t.Sequence[t.Tuple[str, HeaderValue]],
t.Sequence[tuple[str, HeaderValue]],
]
# The possible types returned by a route function.
ResponseReturnValue = t.Union[
ResponseValue,
t.Tuple[ResponseValue, HeadersValue],
t.Tuple[ResponseValue, int],
t.Tuple[ResponseValue, int, HeadersValue],
tuple[ResponseValue, HeadersValue],
tuple[ResponseValue, int],
tuple[ResponseValue, int, HeadersValue],
"WSGIApplication",
]
@ -56,21 +56,21 @@ BeforeRequestCallable = t.Union[
t.Callable[[], 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[
t.Callable[[t.Optional[BaseException]], None],
t.Callable[[t.Optional[BaseException]], t.Awaitable[None]],
]
TemplateContextProcessorCallable = t.Union[
t.Callable[[], t.Dict[str, t.Any]],
t.Callable[[], t.Awaitable[t.Dict[str, t.Any]]],
t.Callable[[], dict[str, t.Any]],
t.Callable[[], t.Awaitable[dict[str, t.Any]]],
]
TemplateFilterCallable = t.Callable[..., t.Any]
TemplateGlobalCallable = t.Callable[..., t.Any]
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[
[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

View file

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