Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

drop support for Python 3.8 #5623

Merged
merged 1 commit into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 0 additions & 6 deletions docs/async-await.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion docs/extensiondev.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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
Expand All @@ -94,7 +94,7 @@ module = [
ignore_missing_imports = true

[tool.pyright]
pythonVersion = "3.8"
pythonVersion = "3.9"
include = ["src/flask", "tests/typing"]
typeCheckingMode = "basic"

Expand Down
2 changes: 1 addition & 1 deletion src/flask/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -1245,7 +1245,7 @@ def make_response(self, rv: ft.ResponseReturnValue) -> Response:

# extend existing headers with provided headers
if headers:
rv.headers.update(headers) # type: ignore[arg-type]
rv.headers.update(headers)

return rv

Expand Down
5 changes: 1 addition & 4 deletions src/flask/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,23 +150,20 @@ def from_prefixed_env(
.. 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)
except Exception:
# 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
Expand Down
4 changes: 2 additions & 2 deletions src/flask/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
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
Expand Down Expand Up @@ -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]

Expand Down
13 changes: 2 additions & 11 deletions src/flask/sansio/scaffold.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(".")
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down
22 changes: 11 additions & 11 deletions src/flask/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"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],
Expand All @@ -21,21 +21,21 @@

# 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",
]

Expand All @@ -56,21 +56,21 @@
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
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tox]
envlist =
py3{13,12,11,10,9,8}
py3{13,12,11,10,9}
pypy310
py312-min
py39-dev
Expand Down