Skip to content
Closed
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 @@ -19,7 +19,6 @@ jobs:
- {python: '3.12'}
- {python: '3.11'}
- {python: '3.10'}
- {python: '3.9'}
- {name: PyPy, python: 'pypy-3.11', tox: pypy3.11}
- {name: Minimum Versions, python: '3.13', tox: tests-min}
- {name: Development Versions, python: '3.10', tox: tests-dev}
Expand Down
11 changes: 11 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
Version 3.2.0
-------------

Unreleased

- Drop support for Python 3.9. :pr:`5730`
- Remove previously deprecated code: ``__version__``. :pr:`5648`
- ``template_filter``, ``template_test``, and ``template_global`` decorators
can be used without parentheses. :issue:`5729`


Version 3.1.2
-------------

Expand Down
6 changes: 4 additions & 2 deletions docs/extensiondev.rst
Original file line number Diff line number Diff line change
Expand Up @@ -294,11 +294,13 @@ 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.9 as of October 2024, and this will update
over time.
Flask and Pallets policy is to support all Python versions that are not
within six months of end of life (EOL). See Python's `EOL calendar`_ for
timing.

.. _PyPI: https://pypi.org/search/?c=Framework+%3A%3A+Flask
.. _Discord Chat: https://discord.gg/pallets
.. _GitHub Discussions: https://github.com/pallets/flask/discussions
.. _Official Pallets Themes: https://pypi.org/project/Pallets-Sphinx-Themes/
.. _Pallets-Eco: https://github.com/pallets-eco
.. _EOL calendar: https://devguide.python.org/versions/
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.9 and newer.
We recommend using the latest version of Python. Flask supports Python 3.10 and newer.


Dependencies
Expand Down
60 changes: 43 additions & 17 deletions docs/templating.rst
Original file line number Diff line number Diff line change
Expand Up @@ -137,31 +137,57 @@ using in this block.

.. _registering-filters:

Registering Filters
-------------------
Registering Filters, Tests, and Globals
---------------------------------------

If you want to register your own filters in Jinja you have two ways to do
that. You can either put them by hand into the
:attr:`~flask.Flask.jinja_env` of the application or use the
:meth:`~flask.Flask.template_filter` decorator.
The Flask app and blueprints provide decorators and methods to register your own
filters, tests, and global functions for use in Jinja templates. They all follow
the same pattern, so the following examples only discuss filters.

The two following examples work the same and both reverse an object::
Decorate a function with :meth:`~.Flask.template_filter` to register it as a
template filter.

@app.template_filter('reverse')
.. code-block:: python

@app.template_filter
def reverse(s):
return reversed(s)

.. code-block:: jinja

{% for item in data | reverse %}
{% endfor %}

By default it will use the name of the function as the name of the filter, but
that can be changed by passing a name to the decorator.

.. code-block:: python

@app.template_filter("reverse")
def reverse_filter(s):
return s[::-1]
return reversed(s)

A filter can be registered separately using :meth:`~.Flask.add_template_filter`.
The name is optional and will use the function name if not given.

.. code-block:: python

def reverse_filter(s):
return s[::-1]
app.jinja_env.filters['reverse'] = reverse_filter
return reversed(s)

In case of the decorator the argument is optional if you want to use the
function name as name of the filter. Once registered, you can use the filter
in your templates in the same way as Jinja's builtin filters, for example if
you have a Python list in context called `mylist`::
app.add_template_filter(reverse_filter, "reverse")

{% for x in mylist | reverse %}
{% endfor %}
For template tests, use the :meth:`~.Flask.template_test` decorator or
:meth:`~.Flask.add_template_test` method. For template global functions, use the
:meth:`~.Flask.template_global` decorator or :meth:`~.Flask.add_template_global`
method.

The same methods also exist on :class:`.Blueprint`, prefixed with ``app_`` to
indicate that the registered functions will be avaialble to all templates, not
only when rendering from within the blueprint.

The Jinja environment is also available as :attr:`~.Flask.jinja_env`. It may be
modified directly, as you would when using Jinja outside Flask.


Context Processors
Expand Down
16 changes: 9 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "Flask"
version = "3.1.2"
version = "3.2.0.dev"
description = "A simple framework for building complex web applications."
readme = "README.md"
license = "BSD-3-Clause"
Expand All @@ -19,11 +19,10 @@ classifiers = [
"Topic :: Software Development :: Libraries :: Application Frameworks",
"Typing :: Typed",
]
requires-python = ">=3.9"
requires-python = ">=3.10"
dependencies = [
"blinker>=1.9.0",
"click>=8.1.3",
"importlib-metadata>=3.6.0; python_version < '3.10'",
"itsdangerous>=2.2.0",
"jinja2>=3.1.2",
"markupsafe>=2.1.1",
Expand Down Expand Up @@ -58,7 +57,7 @@ pre-commit = [
]
tests = [
"asgiref",
"greenlet ; python_version < '3.11'",
"greenlet",
"pytest",
"python-dotenv",
]
Expand Down Expand Up @@ -126,7 +125,7 @@ exclude_also = [
]

[tool.mypy]
python_version = "3.9"
python_version = "3.10"
files = ["src", "tests/type_check"]
show_error_codes = true
pretty = true
Expand All @@ -142,7 +141,7 @@ module = [
ignore_missing_imports = true

[tool.pyright]
pythonVersion = "3.9"
pythonVersion = "3.10"
include = ["src", "tests/type_check"]
typeCheckingMode = "basic"

Expand All @@ -161,14 +160,17 @@ select = [
"UP", # pyupgrade
"W", # pycodestyle warning
]
ignore = [
"UP038", # keep isinstance tuple
]

[tool.ruff.lint.isort]
force-single-line = true
order-by-type = false

[tool.tox]
env_list = [
"py3.13", "py3.12", "py3.11", "py3.10", "py3.9",
"py3.13", "py3.12", "py3.11", "py3.10",
"pypy3.11",
"tests-min", "tests-dev",
"style",
Expand Down
22 changes: 0 additions & 22 deletions src/flask/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
from __future__ import annotations

import typing as t

from . import json as json
from .app import Flask as Flask
from .blueprints import Blueprint as Blueprint
Expand Down Expand Up @@ -41,21 +37,3 @@
from .templating import stream_template_string as stream_template_string
from .wrappers import Request as Request
from .wrappers import Response as Response

if not t.TYPE_CHECKING:

def __getattr__(name: str) -> t.Any:
if name == "__version__":
import importlib.metadata
import warnings

warnings.warn(
"The '__version__' attribute is deprecated and will be removed in"
" Flask 3.2. Use feature detection or"
" 'importlib.metadata.version(\"flask\")' instead.",
DeprecationWarning,
stacklevel=2,
)
return importlib.metadata.version("flask")

raise AttributeError(name)
10 changes: 1 addition & 9 deletions src/flask/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -601,15 +601,7 @@ def _load_plugin_commands(self) -> None:
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 # pyright: ignore

for ep in metadata.entry_points(group="flask.commands"):
for ep in importlib.metadata.entry_points(group="flask.commands"):
self.add_command(ep.load(), ep.name)

self._loaded_plugin_commands = True
Expand Down
2 changes: 1 addition & 1 deletion src/flask/json/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ class DefaultJSONProvider(JSONProvider):
method) will call the ``__html__`` method to get a string.
"""

default: t.Callable[[t.Any], t.Any] = staticmethod(_default) # type: ignore[assignment]
default: t.Callable[[t.Any], t.Any] = staticmethod(_default)
"""Apply this function to any object that :meth:`json.dumps` does
not know how to serialize. It should return a valid JSON type or
raise a ``TypeError``.
Expand Down
Loading
Loading