", view_func=item)
+ app.add_url_rule(f"/{name}/", view_func=group)
register_api(app, User, "users")
register_api(app, Story, "stories")
diff --git a/examples/celery/README.md b/examples/celery/README.md
new file mode 100644
index 0000000000..038eb51eb6
--- /dev/null
+++ b/examples/celery/README.md
@@ -0,0 +1,27 @@
+Background Tasks with Celery
+============================
+
+This example shows how to configure Celery with Flask, how to set up an API for
+submitting tasks and polling results, and how to use that API with JavaScript. See
+[Flask's documentation about Celery](https://flask.palletsprojects.com/patterns/celery/).
+
+From this directory, create a virtualenv and install the application into it. Then run a
+Celery worker.
+
+```shell
+$ python3 -m venv .venv
+$ . ./.venv/bin/activate
+$ pip install -r requirements.txt && pip install -e .
+$ celery -A make_celery worker --loglevel INFO
+```
+
+In a separate terminal, activate the virtualenv and run the Flask development server.
+
+```shell
+$ . ./.venv/bin/activate
+$ flask -A task_app run --debug
+```
+
+Go to http://localhost:5000/ and use the forms to submit tasks. You can see the polling
+requests in the browser dev tools and the Flask logs. You can see the tasks submitting
+and completing in the Celery logs.
diff --git a/examples/celery/make_celery.py b/examples/celery/make_celery.py
new file mode 100644
index 0000000000..f7d138e642
--- /dev/null
+++ b/examples/celery/make_celery.py
@@ -0,0 +1,4 @@
+from task_app import create_app
+
+flask_app = create_app()
+celery_app = flask_app.extensions["celery"]
diff --git a/examples/celery/pyproject.toml b/examples/celery/pyproject.toml
new file mode 100644
index 0000000000..88ba6b960c
--- /dev/null
+++ b/examples/celery/pyproject.toml
@@ -0,0 +1,11 @@
+[project]
+name = "flask-example-celery"
+version = "1.0.0"
+description = "Example Flask application with Celery background tasks."
+readme = "README.md"
+requires-python = ">=3.7"
+dependencies = ["flask>=2.2.2", "celery[redis]>=5.2.7"]
+
+[build-system]
+requires = ["setuptools"]
+build-backend = "setuptools.build_meta"
diff --git a/examples/celery/requirements.txt b/examples/celery/requirements.txt
new file mode 100644
index 0000000000..b283401366
--- /dev/null
+++ b/examples/celery/requirements.txt
@@ -0,0 +1,56 @@
+#
+# This file is autogenerated by pip-compile with Python 3.10
+# by the following command:
+#
+# pip-compile pyproject.toml
+#
+amqp==5.1.1
+ # via kombu
+async-timeout==4.0.2
+ # via redis
+billiard==3.6.4.0
+ # via celery
+celery[redis]==5.2.7
+ # via flask-example-celery (pyproject.toml)
+click==8.1.3
+ # via
+ # celery
+ # click-didyoumean
+ # click-plugins
+ # click-repl
+ # flask
+click-didyoumean==0.3.0
+ # via celery
+click-plugins==1.1.1
+ # via celery
+click-repl==0.2.0
+ # via celery
+flask==2.2.2
+ # via flask-example-celery (pyproject.toml)
+itsdangerous==2.1.2
+ # via flask
+jinja2==3.1.2
+ # via flask
+kombu==5.2.4
+ # via celery
+markupsafe==2.1.2
+ # via
+ # jinja2
+ # werkzeug
+prompt-toolkit==3.0.36
+ # via click-repl
+pytz==2022.7.1
+ # via celery
+redis==4.5.1
+ # via celery
+six==1.16.0
+ # via click-repl
+vine==5.0.0
+ # via
+ # amqp
+ # celery
+ # kombu
+wcwidth==0.2.6
+ # via prompt-toolkit
+werkzeug==2.2.2
+ # via flask
diff --git a/examples/celery/src/task_app/__init__.py b/examples/celery/src/task_app/__init__.py
new file mode 100644
index 0000000000..dafff8aad8
--- /dev/null
+++ b/examples/celery/src/task_app/__init__.py
@@ -0,0 +1,39 @@
+from celery import Celery
+from celery import Task
+from flask import Flask
+from flask import render_template
+
+
+def create_app() -> Flask:
+ app = Flask(__name__)
+ app.config.from_mapping(
+ CELERY=dict(
+ broker_url="redis://localhost",
+ result_backend="redis://localhost",
+ task_ignore_result=True,
+ ),
+ )
+ app.config.from_prefixed_env()
+ celery_init_app(app)
+
+ @app.route("/")
+ def index() -> str:
+ return render_template("index.html")
+
+ from . import views
+
+ app.register_blueprint(views.bp)
+ return app
+
+
+def celery_init_app(app: Flask) -> Celery:
+ class FlaskTask(Task):
+ def __call__(self, *args: object, **kwargs: object) -> object:
+ with app.app_context():
+ return self.run(*args, **kwargs)
+
+ celery_app = Celery(app.name, task_cls=FlaskTask)
+ celery_app.config_from_object(app.config["CELERY"])
+ celery_app.set_default()
+ app.extensions["celery"] = celery_app
+ return celery_app
diff --git a/examples/celery/src/task_app/tasks.py b/examples/celery/src/task_app/tasks.py
new file mode 100644
index 0000000000..b6b3595d22
--- /dev/null
+++ b/examples/celery/src/task_app/tasks.py
@@ -0,0 +1,23 @@
+import time
+
+from celery import shared_task
+from celery import Task
+
+
+@shared_task(ignore_result=False)
+def add(a: int, b: int) -> int:
+ return a + b
+
+
+@shared_task()
+def block() -> None:
+ time.sleep(5)
+
+
+@shared_task(bind=True, ignore_result=False)
+def process(self: Task, total: int) -> object:
+ for i in range(total):
+ self.update_state(state="PROGRESS", meta={"current": i + 1, "total": total})
+ time.sleep(1)
+
+ return {"current": total, "total": total}
diff --git a/examples/celery/src/task_app/templates/index.html b/examples/celery/src/task_app/templates/index.html
new file mode 100644
index 0000000000..4e1145cb8f
--- /dev/null
+++ b/examples/celery/src/task_app/templates/index.html
@@ -0,0 +1,108 @@
+
+
+
+
+ Codestin Search App
+
+
+Celery Example
+Execute background tasks with Celery. Submits tasks and shows results using JavaScript.
+
+
+Add
+Start a task to add two numbers, then poll for the result.
+
+Result:
+
+
+Block
+Start a task that takes 5 seconds. However, the response will return immediately.
+
+
+
+
+Process
+Start a task that counts, waiting one second each time, showing progress.
+
+
+
+
+
+
diff --git a/examples/celery/src/task_app/views.py b/examples/celery/src/task_app/views.py
new file mode 100644
index 0000000000..99cf92dc20
--- /dev/null
+++ b/examples/celery/src/task_app/views.py
@@ -0,0 +1,38 @@
+from celery.result import AsyncResult
+from flask import Blueprint
+from flask import request
+
+from . import tasks
+
+bp = Blueprint("tasks", __name__, url_prefix="/tasks")
+
+
+@bp.get("/result/")
+def result(id: str) -> dict[str, object]:
+ result = AsyncResult(id)
+ ready = result.ready()
+ return {
+ "ready": ready,
+ "successful": result.successful() if ready else None,
+ "value": result.get() if ready else result.result,
+ }
+
+
+@bp.post("/add")
+def add() -> dict[str, object]:
+ a = request.form.get("a", type=int)
+ b = request.form.get("b", type=int)
+ result = tasks.add.delay(a, b)
+ return {"result_id": result.id}
+
+
+@bp.post("/block")
+def block() -> dict[str, object]:
+ result = tasks.block.delay()
+ return {"result_id": result.id}
+
+
+@bp.post("/process")
+def process() -> dict[str, object]:
+ result = tasks.process.delay(total=request.form.get("total", type=int))
+ return {"result_id": result.id}
diff --git a/examples/javascript/js_example/__init__.py b/examples/javascript/js_example/__init__.py
index 068b2d98ed..0ec3ca215a 100644
--- a/examples/javascript/js_example/__init__.py
+++ b/examples/javascript/js_example/__init__.py
@@ -2,4 +2,4 @@
app = Flask(__name__)
-from js_example import views # noqa: F401
+from js_example import views # noqa: E402, F401
diff --git a/examples/tutorial/README.rst b/examples/tutorial/README.rst
index a7e12ca250..1c745078bc 100644
--- a/examples/tutorial/README.rst
+++ b/examples/tutorial/README.rst
@@ -48,7 +48,7 @@ Run
.. code-block:: text
$ flask --app flaskr init-db
- $ flask --app flaskr --debug run
+ $ flask --app flaskr run --debug
Open http://127.0.0.1:5000 in a browser.
diff --git a/requirements/build.in b/requirements/build.in
new file mode 100644
index 0000000000..378eac25d3
--- /dev/null
+++ b/requirements/build.in
@@ -0,0 +1 @@
+build
diff --git a/requirements/build.txt b/requirements/build.txt
new file mode 100644
index 0000000000..a735b3d0d1
--- /dev/null
+++ b/requirements/build.txt
@@ -0,0 +1,17 @@
+# SHA1:80754af91bfb6d1073585b046fe0a474ce868509
+#
+# This file is autogenerated by pip-compile-multi
+# To update, run:
+#
+# pip-compile-multi
+#
+build==0.9.0
+ # via -r requirements/build.in
+packaging==23.0
+ # via build
+pep517==0.13.0
+ # via build
+tomli==2.0.1
+ # via
+ # build
+ # pep517
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 03d224c15c..41b2619ce3 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -8,53 +8,57 @@
-r docs.txt
-r tests.txt
-r typing.txt
-build==0.8.0
+build==0.9.0
# via pip-tools
+cachetools==5.2.0
+ # via tox
cfgv==3.3.1
# via pre-commit
+chardet==5.1.0
+ # via tox
click==8.1.3
# via
# pip-compile-multi
# pip-tools
-distlib==0.3.5
+colorama==0.4.6
+ # via tox
+distlib==0.3.6
# via virtualenv
-filelock==3.7.1
+filelock==3.8.2
# via
# tox
# virtualenv
-greenlet==1.1.2 ; python_version < "3.11"
- # via -r requirements/tests.in
-identify==2.5.3
+identify==2.5.11
# via pre-commit
nodeenv==1.7.0
# via pre-commit
pep517==0.13.0
# via build
-pip-compile-multi==2.4.6
+pip-compile-multi==2.6.1
# via -r requirements/dev.in
-pip-tools==6.8.0
+pip-tools==6.12.1
# via pip-compile-multi
-platformdirs==2.5.2
- # via virtualenv
+platformdirs==2.6.0
+ # via
+ # tox
+ # virtualenv
pre-commit==2.20.0
# via -r requirements/dev.in
+pyproject-api==1.2.1
+ # via tox
pyyaml==6.0
# via pre-commit
-six==1.16.0
- # via tox
toml==0.10.2
- # via
- # pre-commit
- # tox
+ # via pre-commit
toposort==1.7
# via pip-compile-multi
-tox==3.25.1
+tox==4.0.16
# via -r requirements/dev.in
-virtualenv==20.16.2
+virtualenv==20.17.1
# via
# pre-commit
# tox
-wheel==0.37.1
+wheel==0.38.4
# via pip-tools
# The following packages are considered to be unsafe in a requirements file:
diff --git a/requirements/docs.txt b/requirements/docs.txt
index 2ac5f547e4..b1e46bde86 100644
--- a/requirements/docs.txt
+++ b/requirements/docs.txt
@@ -7,17 +7,17 @@
#
alabaster==0.7.12
# via sphinx
-babel==2.10.3
+babel==2.11.0
# via sphinx
-certifi==2022.6.15
+certifi==2022.12.7
# via requests
-charset-normalizer==2.1.0
+charset-normalizer==2.1.1
# via requests
docutils==0.17.1
# via
# sphinx
# sphinx-tabs
-idna==3.3
+idna==3.4
# via requests
imagesize==1.4.1
# via sphinx
@@ -25,19 +25,17 @@ jinja2==3.1.2
# via sphinx
markupsafe==2.1.1
# via jinja2
-packaging==21.3
+packaging==22.0
# via
# pallets-sphinx-themes
# sphinx
-pallets-sphinx-themes==2.0.2
+pallets-sphinx-themes==2.0.3
# via -r requirements/docs.in
-pygments==2.12.0
+pygments==2.13.0
# via
# sphinx
# sphinx-tabs
-pyparsing==3.0.9
- # via packaging
-pytz==2022.1
+pytz==2022.7
# via babel
requests==2.28.1
# via sphinx
@@ -68,5 +66,5 @@ sphinxcontrib-qthelp==1.0.3
# via sphinx
sphinxcontrib-serializinghtml==1.1.5
# via sphinx
-urllib3==1.26.11
+urllib3==1.26.13
# via requests
diff --git a/requirements/tests-pallets-dev.in b/requirements/tests-pallets-dev.in
deleted file mode 100644
index dddbe48a41..0000000000
--- a/requirements/tests-pallets-dev.in
+++ /dev/null
@@ -1,5 +0,0 @@
-https://github.com/pallets/werkzeug/archive/refs/heads/main.tar.gz
-https://github.com/pallets/jinja/archive/refs/heads/main.tar.gz
-https://github.com/pallets/markupsafe/archive/refs/heads/main.tar.gz
-https://github.com/pallets/itsdangerous/archive/refs/heads/main.tar.gz
-https://github.com/pallets/click/archive/refs/heads/main.tar.gz
diff --git a/requirements/tests-pallets-dev.txt b/requirements/tests-pallets-dev.txt
deleted file mode 100644
index a74f556b17..0000000000
--- a/requirements/tests-pallets-dev.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-# SHA1:692b640e7f835e536628f76de0afff1296524122
-#
-# This file is autogenerated by pip-compile-multi
-# To update, run:
-#
-# pip-compile-multi
-#
-click @ https://github.com/pallets/click/archive/refs/heads/main.tar.gz
- # via -r requirements/tests-pallets-dev.in
-itsdangerous @ https://github.com/pallets/itsdangerous/archive/refs/heads/main.tar.gz
- # via -r requirements/tests-pallets-dev.in
-jinja2 @ https://github.com/pallets/jinja/archive/refs/heads/main.tar.gz
- # via -r requirements/tests-pallets-dev.in
-markupsafe @ https://github.com/pallets/markupsafe/archive/refs/heads/main.tar.gz
- # via
- # -r requirements/tests-pallets-dev.in
- # jinja2
- # werkzeug
-werkzeug @ https://github.com/pallets/werkzeug/archive/refs/heads/main.tar.gz
- # via -r requirements/tests-pallets-dev.in
diff --git a/requirements/tests.txt b/requirements/tests.txt
index d9b7513c33..aff42de283 100644
--- a/requirements/tests.txt
+++ b/requirements/tests.txt
@@ -5,27 +5,19 @@
#
# pip-compile-multi
#
-asgiref==3.5.2
+asgiref==3.6.0
# via -r requirements/tests.in
-attrs==22.1.0
+attrs==22.2.0
# via pytest
blinker==1.5
# via -r requirements/tests.in
-greenlet==1.1.2 ; python_version < "3.11"
- # via -r requirements/tests.in
iniconfig==1.1.1
# via pytest
-packaging==21.3
+packaging==22.0
# via pytest
pluggy==1.0.0
# via pytest
-py==1.11.0
- # via pytest
-pyparsing==3.0.9
- # via packaging
-pytest==7.1.2
+pytest==7.2.0
# via -r requirements/tests.in
-python-dotenv==0.20.0
+python-dotenv==0.21.0
# via -r requirements/tests.in
-tomli==2.0.1
- # via pytest
diff --git a/requirements/typing.txt b/requirements/typing.txt
index a842e1cdd4..ad8dd594df 100644
--- a/requirements/typing.txt
+++ b/requirements/typing.txt
@@ -7,21 +7,19 @@
#
cffi==1.15.1
# via cryptography
-cryptography==37.0.4
+cryptography==38.0.4
# via -r requirements/typing.in
-mypy==0.971
+mypy==0.991
# via -r requirements/typing.in
mypy-extensions==0.4.3
# via mypy
pycparser==2.21
# via cffi
-tomli==2.0.1
- # via mypy
types-contextvars==2.4.7
# via -r requirements/typing.in
types-dataclasses==0.6.6
# via -r requirements/typing.in
-types-setuptools==63.2.3
+types-setuptools==65.6.0.2
# via -r requirements/typing.in
-typing-extensions==4.3.0
+typing-extensions==4.4.0
# via mypy
diff --git a/setup.cfg b/setup.cfg
index e858d13a20..736bd50f27 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -61,31 +61,6 @@ source =
src
*/site-packages
-[flake8]
-# B = bugbear
-# E = pycodestyle errors
-# F = flake8 pyflakes
-# W = pycodestyle warnings
-# B9 = bugbear opinions
-# ISC = implicit str concat
-select = B, E, F, W, B9, ISC
-ignore =
- # slice notation whitespace, invalid
- E203
- # import at top, too many circular import fixes
- E402
- # line length, handled by bugbear B950
- E501
- # bare except, handled by bugbear B001
- E722
- # bin op line break, invalid
- W503
-# up to 88 allowed by bugbear B950
-max-line-length = 80
-per-file-ignores =
- # __init__ exports names
- src/flask/__init__.py: F401
-
[mypy]
files = src/flask, tests/typing
python_version = 3.7
diff --git a/src/flask/__init__.py b/src/flask/__init__.py
index e02531c578..463f55f255 100644
--- a/src/flask/__init__.py
+++ b/src/flask/__init__.py
@@ -42,7 +42,7 @@
from .templating import stream_template as stream_template
from .templating import stream_template_string as stream_template_string
-__version__ = "2.2.2"
+__version__ = "2.2.3"
def __getattr__(name):
diff --git a/src/flask/app.py b/src/flask/app.py
index db442c9edf..0ac4bbb5ae 100644
--- a/src/flask/app.py
+++ b/src/flask/app.py
@@ -384,7 +384,7 @@ def use_x_sendfile(self, value: bool) -> None:
_json_decoder: t.Union[t.Type[json.JSONDecoder], None] = None
@property # type: ignore[override]
- def json_encoder(self) -> t.Type[json.JSONEncoder]: # type: ignore[override]
+ def json_encoder(self) -> t.Type[json.JSONEncoder]:
"""The JSON encoder class to use. Defaults to
:class:`~flask.json.JSONEncoder`.
@@ -423,7 +423,7 @@ def json_encoder(self, value: t.Type[json.JSONEncoder]) -> None:
self._json_encoder = value
@property # type: ignore[override]
- def json_decoder(self) -> t.Type[json.JSONDecoder]: # type: ignore[override]
+ def json_decoder(self) -> t.Type[json.JSONDecoder]:
"""The JSON decoder class to use. Defaults to
:class:`~flask.json.JSONDecoder`.
@@ -558,7 +558,7 @@ def __init__(
static_host: t.Optional[str] = None,
host_matching: bool = False,
subdomain_matching: bool = False,
- template_folder: t.Optional[str] = "templates",
+ template_folder: t.Optional[t.Union[str, os.PathLike]] = "templates",
instance_path: t.Optional[str] = None,
instance_relative_config: bool = False,
root_path: t.Optional[str] = None,
@@ -961,11 +961,14 @@ def select_jinja_autoescape(self, filename: str) -> bool:
"""Returns ``True`` if autoescaping should be active for the given
template name. If no template name is given, returns `True`.
+ .. versionchanged:: 2.2
+ Autoescaping is now enabled by default for ``.svg`` files.
+
.. versionadded:: 0.5
"""
if filename is None:
return True
- return filename.endswith((".html", ".htm", ".xml", ".xhtml"))
+ return filename.endswith((".html", ".htm", ".xml", ".xhtml", ".svg"))
def update_template_context(self, context: dict) -> None:
"""Update the template context with some commonly used variables.
diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py
index 104f8acf0d..eb6642358d 100644
--- a/src/flask/blueprints.py
+++ b/src/flask/blueprints.py
@@ -176,8 +176,8 @@ class Blueprint(Scaffold):
_json_encoder: t.Union[t.Type[json.JSONEncoder], None] = None
_json_decoder: t.Union[t.Type[json.JSONDecoder], None] = None
- @property # type: ignore[override]
- def json_encoder( # type: ignore[override]
+ @property
+ def json_encoder(
self,
) -> t.Union[t.Type[json.JSONEncoder], None]:
"""Blueprint-local JSON encoder class to use. Set to ``None`` to use the app's.
@@ -210,8 +210,8 @@ def json_encoder(self, value: t.Union[t.Type[json.JSONEncoder], None]) -> None:
)
self._json_encoder = value
- @property # type: ignore[override]
- def json_decoder( # type: ignore[override]
+ @property
+ def json_decoder(
self,
) -> t.Union[t.Type[json.JSONDecoder], None]:
"""Blueprint-local JSON decoder class to use. Set to ``None`` to use the app's.
@@ -250,7 +250,7 @@ def __init__(
import_name: str,
static_folder: t.Optional[t.Union[str, os.PathLike]] = None,
static_url_path: t.Optional[str] = None,
- template_folder: t.Optional[str] = None,
+ template_folder: t.Optional[t.Union[str, os.PathLike]] = None,
url_prefix: t.Optional[str] = None,
subdomain: t.Optional[str] = None,
url_defaults: t.Optional[dict] = None,
@@ -478,8 +478,11 @@ def add_url_rule(
provide_automatic_options: t.Optional[bool] = None,
**options: t.Any,
) -> None:
- """Like :meth:`Flask.add_url_rule` but for a blueprint. The endpoint for
- the :func:`url_for` function is prefixed with the name of the blueprint.
+ """Register a URL rule with the blueprint. See :meth:`.Flask.add_url_rule` for
+ full documentation.
+
+ The URL rule is prefixed with the blueprint's URL prefix. The endpoint name,
+ used with :func:`url_for`, is prefixed with the blueprint's name.
"""
if endpoint and "." in endpoint:
raise ValueError("'endpoint' may not contain a dot '.' character.")
@@ -501,8 +504,8 @@ def add_url_rule(
def app_template_filter(
self, name: t.Optional[str] = None
) -> t.Callable[[T_template_filter], T_template_filter]:
- """Register a custom template filter, available application wide. Like
- :meth:`Flask.template_filter` but for a blueprint.
+ """Register a template filter, available in any template rendered by the
+ application. Equivalent to :meth:`.Flask.template_filter`.
:param name: the optional name of the filter, otherwise the
function name will be used.
@@ -518,9 +521,9 @@ def decorator(f: T_template_filter) -> T_template_filter:
def add_app_template_filter(
self, f: ft.TemplateFilterCallable, name: t.Optional[str] = None
) -> None:
- """Register a custom template filter, available application wide. Like
- :meth:`Flask.add_template_filter` but for a blueprint. Works exactly
- like the :meth:`app_template_filter` decorator.
+ """Register a template filter, available in any template rendered by the
+ application. Works like the :meth:`app_template_filter` decorator. Equivalent to
+ :meth:`.Flask.add_template_filter`.
:param name: the optional name of the filter, otherwise the
function name will be used.
@@ -535,8 +538,8 @@ def register_template(state: BlueprintSetupState) -> None:
def app_template_test(
self, name: t.Optional[str] = None
) -> t.Callable[[T_template_test], T_template_test]:
- """Register a custom template test, available application wide. Like
- :meth:`Flask.template_test` but for a blueprint.
+ """Register a template test, available in any template rendered by the
+ application. Equivalent to :meth:`.Flask.template_test`.
.. versionadded:: 0.10
@@ -554,9 +557,9 @@ def decorator(f: T_template_test) -> T_template_test:
def add_app_template_test(
self, f: ft.TemplateTestCallable, name: t.Optional[str] = None
) -> None:
- """Register a custom template test, available application wide. Like
- :meth:`Flask.add_template_test` but for a blueprint. Works exactly
- like the :meth:`app_template_test` decorator.
+ """Register a template test, available in any template rendered by the
+ application. Works like the :meth:`app_template_test` decorator. Equivalent to
+ :meth:`.Flask.add_template_test`.
.. versionadded:: 0.10
@@ -573,8 +576,8 @@ def register_template(state: BlueprintSetupState) -> None:
def app_template_global(
self, name: t.Optional[str] = None
) -> t.Callable[[T_template_global], T_template_global]:
- """Register a custom template global, available application wide. Like
- :meth:`Flask.template_global` but for a blueprint.
+ """Register a template global, available in any template rendered by the
+ application. Equivalent to :meth:`.Flask.template_global`.
.. versionadded:: 0.10
@@ -592,9 +595,9 @@ def decorator(f: T_template_global) -> T_template_global:
def add_app_template_global(
self, f: ft.TemplateGlobalCallable, name: t.Optional[str] = None
) -> None:
- """Register a custom template global, available application wide. Like
- :meth:`Flask.add_template_global` but for a blueprint. Works exactly
- like the :meth:`app_template_global` decorator.
+ """Register a template global, available in any template rendered by the
+ application. Works like the :meth:`app_template_global` decorator. Equivalent to
+ :meth:`.Flask.add_template_global`.
.. versionadded:: 0.10
@@ -609,8 +612,8 @@ def register_template(state: BlueprintSetupState) -> None:
@setupmethod
def before_app_request(self, f: T_before_request) -> T_before_request:
- """Like :meth:`Flask.before_request`. Such a function is executed
- before each request, even if outside of a blueprint.
+ """Like :meth:`before_request`, but before every request, not only those handled
+ by the blueprint. Equivalent to :meth:`.Flask.before_request`.
"""
self.record_once(
lambda s: s.app.before_request_funcs.setdefault(None, []).append(f)
@@ -621,8 +624,8 @@ def before_app_request(self, f: T_before_request) -> T_before_request:
def before_app_first_request(
self, f: T_before_first_request
) -> T_before_first_request:
- """Like :meth:`Flask.before_first_request`. Such a function is
- executed before the first request to the application.
+ """Register a function to run before the first request to the application is
+ handled by the worker. Equivalent to :meth:`.Flask.before_first_request`.
.. deprecated:: 2.2
Will be removed in Flask 2.3. Run setup code when creating
@@ -642,8 +645,8 @@ def before_app_first_request(
@setupmethod
def after_app_request(self, f: T_after_request) -> T_after_request:
- """Like :meth:`Flask.after_request` but for a blueprint. Such a function
- is executed after each request, even if outside of the blueprint.
+ """Like :meth:`after_request`, but after every request, not only those handled
+ by the blueprint. Equivalent to :meth:`.Flask.after_request`.
"""
self.record_once(
lambda s: s.app.after_request_funcs.setdefault(None, []).append(f)
@@ -652,9 +655,8 @@ def after_app_request(self, f: T_after_request) -> T_after_request:
@setupmethod
def teardown_app_request(self, f: T_teardown) -> T_teardown:
- """Like :meth:`Flask.teardown_request` but for a blueprint. Such a
- function is executed when tearing down each request, even if outside of
- the blueprint.
+ """Like :meth:`teardown_request`, but after every request, not only those
+ handled by the blueprint. Equivalent to :meth:`.Flask.teardown_request`.
"""
self.record_once(
lambda s: s.app.teardown_request_funcs.setdefault(None, []).append(f)
@@ -665,8 +667,8 @@ def teardown_app_request(self, f: T_teardown) -> T_teardown:
def app_context_processor(
self, f: T_template_context_processor
) -> T_template_context_processor:
- """Like :meth:`Flask.context_processor` but for a blueprint. Such a
- function is executed each request, even if outside of the blueprint.
+ """Like :meth:`context_processor`, but for templates rendered by every view, not
+ only by the blueprint. Equivalent to :meth:`.Flask.context_processor`.
"""
self.record_once(
lambda s: s.app.template_context_processors.setdefault(None, []).append(f)
@@ -677,8 +679,8 @@ def app_context_processor(
def app_errorhandler(
self, code: t.Union[t.Type[Exception], int]
) -> t.Callable[[T_error_handler], T_error_handler]:
- """Like :meth:`Flask.errorhandler` but for a blueprint. This
- handler is used for all requests, even if outside of the blueprint.
+ """Like :meth:`errorhandler`, but for every request, not only those handled by
+ the blueprint. Equivalent to :meth:`.Flask.errorhandler`.
"""
def decorator(f: T_error_handler) -> T_error_handler:
@@ -691,7 +693,9 @@ def decorator(f: T_error_handler) -> T_error_handler:
def app_url_value_preprocessor(
self, f: T_url_value_preprocessor
) -> T_url_value_preprocessor:
- """Same as :meth:`url_value_preprocessor` but application wide."""
+ """Like :meth:`url_value_preprocessor`, but for every request, not only those
+ handled by the blueprint. Equivalent to :meth:`.Flask.url_value_preprocessor`.
+ """
self.record_once(
lambda s: s.app.url_value_preprocessors.setdefault(None, []).append(f)
)
@@ -699,7 +703,9 @@ def app_url_value_preprocessor(
@setupmethod
def app_url_defaults(self, f: T_url_defaults) -> T_url_defaults:
- """Same as :meth:`url_defaults` but application wide."""
+ """Like :meth:`url_defaults`, but for every request, not only those handled by
+ the blueprint. Equivalent to :meth:`.Flask.url_defaults`.
+ """
self.record_once(
lambda s: s.app.url_default_functions.setdefault(None, []).append(f)
)
diff --git a/src/flask/cli.py b/src/flask/cli.py
index 82fe8194eb..37a15ff2d8 100644
--- a/src/flask/cli.py
+++ b/src/flask/cli.py
@@ -933,6 +933,9 @@ def app(environ, start_response):
)
+run_command.params.insert(0, _debug_option)
+
+
@click.command("shell", short_help="Run a shell in the app context.")
@with_appcontext
def shell_command() -> None:
diff --git a/src/flask/config.py b/src/flask/config.py
index 7b6a137ada..d4fc310fe3 100644
--- a/src/flask/config.py
+++ b/src/flask/config.py
@@ -275,8 +275,9 @@ def from_file(
def from_mapping(
self, mapping: t.Optional[t.Mapping[str, t.Any]] = None, **kwargs: t.Any
) -> bool:
- """Updates the config like :meth:`update` ignoring items with non-upper
- keys.
+ """Updates the config like :meth:`update` ignoring items with
+ non-upper keys.
+
:return: Always returns ``True``.
.. versionadded:: 0.11
diff --git a/src/flask/ctx.py b/src/flask/ctx.py
index ca2844944c..c79c26dc96 100644
--- a/src/flask/ctx.py
+++ b/src/flask/ctx.py
@@ -307,7 +307,7 @@ def __init__(
self.app = app
if request is None:
request = app.request_class(environ)
- request.json_module = app.json # type: ignore[misc]
+ request.json_module = app.json
self.request: Request = request
self.url_adapter = None
try:
diff --git a/src/flask/globals.py b/src/flask/globals.py
index b230ef7e06..254da42b98 100644
--- a/src/flask/globals.py
+++ b/src/flask/globals.py
@@ -88,7 +88,7 @@ def __getattr__(name: str) -> t.Any:
import warnings
warnings.warn(
- "'_app_ctx_stack' is deprecated and will be remoevd in Flask 2.3.",
+ "'_app_ctx_stack' is deprecated and will be removed in Flask 2.3.",
DeprecationWarning,
stacklevel=2,
)
@@ -98,7 +98,7 @@ def __getattr__(name: str) -> t.Any:
import warnings
warnings.warn(
- "'_request_ctx_stack' is deprecated and will be remoevd in Flask 2.3.",
+ "'_request_ctx_stack' is deprecated and will be removed in Flask 2.3.",
DeprecationWarning,
stacklevel=2,
)
diff --git a/src/flask/helpers.py b/src/flask/helpers.py
index 15990d0e82..3833cb8a0a 100644
--- a/src/flask/helpers.py
+++ b/src/flask/helpers.py
@@ -149,7 +149,7 @@ def generator() -> t.Generator:
yield from gen
finally:
if hasattr(gen, "close"):
- gen.close() # type: ignore
+ gen.close()
# The trick is to start the generator. Then the code execution runs until
# the first dummy None is yielded at which point the context was already
@@ -287,7 +287,7 @@ def redirect(
return _wz_redirect(location, code=code, Response=Response)
-def abort( # type: ignore[misc]
+def abort(
code: t.Union[int, "BaseResponse"], *args: t.Any, **kwargs: t.Any
) -> "te.NoReturn":
"""Raise an :exc:`~werkzeug.exceptions.HTTPException` for the given
@@ -617,7 +617,7 @@ def get_root_path(import_name: str) -> str:
return os.getcwd()
if hasattr(loader, "get_filename"):
- filepath = loader.get_filename(import_name) # type: ignore
+ filepath = loader.get_filename(import_name)
else:
# Fall back to imports.
__import__(import_name)
diff --git a/src/flask/scaffold.py b/src/flask/scaffold.py
index 1530a11ec8..7277b33ad3 100644
--- a/src/flask/scaffold.py
+++ b/src/flask/scaffold.py
@@ -93,7 +93,7 @@ def __init__(
import_name: str,
static_folder: t.Optional[t.Union[str, os.PathLike]] = None,
static_url_path: t.Optional[str] = None,
- template_folder: t.Optional[str] = None,
+ template_folder: t.Optional[t.Union[str, os.PathLike]] = None,
root_path: t.Optional[str] = None,
):
#: The name of the package or module that this object belongs
@@ -561,6 +561,11 @@ def load_user():
a non-``None`` value, the value is handled as if it was the
return value from the view, and further request handling is
stopped.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ executes before every request. When used on a blueprint, this executes before
+ every request that the blueprint handles. To register with a blueprint and
+ execute before every request, use :meth:`.Blueprint.before_app_request`.
"""
self.before_request_funcs.setdefault(None, []).append(f)
return f
@@ -577,6 +582,11 @@ def after_request(self, f: T_after_request) -> T_after_request:
``after_request`` functions will not be called. Therefore, this
should not be used for actions that must execute, such as to
close resources. Use :meth:`teardown_request` for that.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ executes after every request. When used on a blueprint, this executes after
+ every request that the blueprint handles. To register with a blueprint and
+ execute after every request, use :meth:`.Blueprint.after_app_request`.
"""
self.after_request_funcs.setdefault(None, []).append(f)
return f
@@ -606,6 +616,11 @@ def teardown_request(self, f: T_teardown) -> T_teardown:
``try``/``except`` block and log any errors.
The return values of teardown functions are ignored.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ executes after every request. When used on a blueprint, this executes after
+ every request that the blueprint handles. To register with a blueprint and
+ execute after every request, use :meth:`.Blueprint.teardown_app_request`.
"""
self.teardown_request_funcs.setdefault(None, []).append(f)
return f
@@ -615,7 +630,15 @@ def context_processor(
self,
f: T_template_context_processor,
) -> T_template_context_processor:
- """Registers a template context processor function."""
+ """Registers a template context processor function. These functions run before
+ rendering a template. The keys of the returned dict are added as variables
+ available in the template.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ is called for every rendered template. When used on a blueprint, this is called
+ for templates rendered from the blueprint's views. To register with a blueprint
+ and affect every template, use :meth:`.Blueprint.app_context_processor`.
+ """
self.template_context_processors[None].append(f)
return f
@@ -635,6 +658,11 @@ def url_value_preprocessor(
The function is passed the endpoint name and values dict. The return
value is ignored.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ is called for every request. When used on a blueprint, this is called for
+ requests that the blueprint handles. To register with a blueprint and affect
+ every request, use :meth:`.Blueprint.app_url_value_preprocessor`.
"""
self.url_value_preprocessors[None].append(f)
return f
@@ -644,6 +672,11 @@ def url_defaults(self, f: T_url_defaults) -> T_url_defaults:
"""Callback function for URL defaults for all view functions of the
application. It's called with the endpoint and values and should
update the values passed in place.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ is called for every request. When used on a blueprint, this is called for
+ requests that the blueprint handles. To register with a blueprint and affect
+ every request, use :meth:`.Blueprint.app_url_defaults`.
"""
self.url_default_functions[None].append(f)
return f
@@ -667,6 +700,11 @@ def page_not_found(error):
def special_exception_handler(error):
return 'Database connection failed', 500
+ This is available on both app and blueprint objects. When used on an app, this
+ can handle errors from every request. When used on a blueprint, this can handle
+ errors from requests that the blueprint handles. To register with a blueprint
+ and affect every request, use :meth:`.Blueprint.app_errorhandler`.
+
.. versionadded:: 0.7
Use :meth:`register_error_handler` instead of modifying
:attr:`error_handler_spec` directly, for application wide error
diff --git a/src/flask/testing.py b/src/flask/testing.py
index ec9ebb9def..3b21b093fb 100644
--- a/src/flask/testing.py
+++ b/src/flask/testing.py
@@ -225,7 +225,7 @@ def open(
buffered=buffered,
follow_redirects=follow_redirects,
)
- response.json_module = self.application.json # type: ignore[misc]
+ response.json_module = self.application.json # type: ignore[assignment]
# Re-push contexts that were preserved during the request.
while self._new_contexts:
diff --git a/src/flask/views.py b/src/flask/views.py
index a82f191238..f86172b43c 100644
--- a/src/flask/views.py
+++ b/src/flask/views.py
@@ -92,8 +92,8 @@ def as_view(
:attr:`init_every_request` to ``False``, the same instance will
be used for every request.
- The arguments passed to this method are forwarded to the view
- class ``__init__`` method.
+ Except for ``name``, all other arguments passed to this method
+ are forwarded to the view class ``__init__`` method.
.. versionchanged:: 2.2
Added the ``init_every_request`` class attribute.
diff --git a/src/flask/wrappers.py b/src/flask/wrappers.py
index 4b855bfccc..e36a72cb47 100644
--- a/src/flask/wrappers.py
+++ b/src/flask/wrappers.py
@@ -25,7 +25,7 @@ class Request(RequestBase):
specific ones.
"""
- json_module = json
+ json_module: t.Any = json
#: The internal URL rule that matched the request. This can be
#: useful to inspect which methods are allowed for the URL from
diff --git a/tests/test_appctx.py b/tests/test_appctx.py
index f5ca0bde42..aa3a8b4e5c 100644
--- a/tests/test_appctx.py
+++ b/tests/test_appctx.py
@@ -120,14 +120,14 @@ def cleanup(exception):
@app.route("/")
def index():
- raise Exception("dummy")
+ raise ValueError("dummy")
- with pytest.raises(Exception, match="dummy"):
+ with pytest.raises(ValueError, match="dummy"):
with app.app_context():
client.get("/")
assert len(cleanup_stuff) == 1
- assert isinstance(cleanup_stuff[0], Exception)
+ assert isinstance(cleanup_stuff[0], ValueError)
assert str(cleanup_stuff[0]) == "dummy"
diff --git a/tests/test_apps/blueprintapp/__init__.py b/tests/test_apps/blueprintapp/__init__.py
index 4b05798531..ad594cf1da 100644
--- a/tests/test_apps/blueprintapp/__init__.py
+++ b/tests/test_apps/blueprintapp/__init__.py
@@ -2,8 +2,8 @@
app = Flask(__name__)
app.config["DEBUG"] = True
-from blueprintapp.apps.admin import admin
-from blueprintapp.apps.frontend import frontend
+from blueprintapp.apps.admin import admin # noqa: E402
+from blueprintapp.apps.frontend import frontend # noqa: E402
app.register_blueprint(admin)
app.register_blueprint(frontend)
diff --git a/tests/test_basic.py b/tests/test_basic.py
index d547012a5a..9aca667938 100644
--- a/tests/test_basic.py
+++ b/tests/test_basic.py
@@ -1472,11 +1472,11 @@ def test_static_route_with_host_matching():
rv = flask.url_for("static", filename="index.html", _external=True)
assert rv == "http://example.com/static/index.html"
# Providing static_host without host_matching=True should error.
- with pytest.raises(Exception):
+ with pytest.raises(AssertionError):
flask.Flask(__name__, static_host="example.com")
# Providing host_matching=True with static_folder
# but without static_host should error.
- with pytest.raises(Exception):
+ with pytest.raises(AssertionError):
flask.Flask(__name__, host_matching=True)
# Providing host_matching=True without static_host
# but with static_folder=None should not error.
diff --git a/tests/test_reqctx.py b/tests/test_reqctx.py
index abfacb98bf..6c38b66186 100644
--- a/tests/test_reqctx.py
+++ b/tests/test_reqctx.py
@@ -227,7 +227,6 @@ def index():
def test_session_dynamic_cookie_name():
-
# This session interface will use a cookie with a different name if the
# requested url ends with the string "dynamic_cookie"
class PathAwareSessionInterface(SecureCookieSessionInterface):
diff --git a/tests/typing/typing_app_decorators.py b/tests/typing/typing_app_decorators.py
index 3df3e716fb..6b2188aa60 100644
--- a/tests/typing/typing_app_decorators.py
+++ b/tests/typing/typing_app_decorators.py
@@ -10,12 +10,12 @@
@app.after_request
def after_sync(response: Response) -> Response:
- ...
+ return Response()
@app.after_request
async def after_async(response: Response) -> Response:
- ...
+ return Response()
@app.before_request
diff --git a/tox.ini b/tox.ini
index ee4d40f689..08c6dca2f5 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,7 +1,8 @@
[tox]
envlist =
- py3{11,10,9,8,7},pypy3{8,7}
- py310-min
+ py3{12,11,10,9,8,7}
+ pypy3{9,8,7}
+ py311-min
py37-dev
style
typing
@@ -9,12 +10,17 @@ envlist =
skip_missing_interpreters = true
[testenv]
+package = wheel
+wheel_build_env = .pkg
envtmpdir = {toxworkdir}/tmp/{envname}
deps =
-r requirements/tests.txt
min: -r requirements/tests-pallets-min.txt
- dev: -r requirements/tests-pallets-dev.txt
-
+ dev: https://github.com/pallets/werkzeug/archive/refs/heads/main.tar.gz
+ dev: https://github.com/pallets/jinja/archive/refs/heads/main.tar.gz
+ dev: https://github.com/pallets/markupsafe/archive/refs/heads/main.tar.gz
+ dev: https://github.com/pallets/itsdangerous/archive/refs/heads/main.tar.gz
+ dev: https://github.com/pallets/click/archive/refs/heads/main.tar.gz
# examples/tutorial[test]
# examples/javascript[test]
# commands = pytest -v --tb=short --basetemp={envtmpdir} {posargs:tests examples}
@@ -23,12 +29,16 @@ commands = pytest -v --tb=short --basetemp={envtmpdir} {posargs:tests}
[testenv:style]
deps = pre-commit
skip_install = true
-commands = pre-commit run --all-files --show-diff-on-failure
+commands = pre-commit run --all-files
[testenv:typing]
+package = wheel
+wheel_build_env = .pkg
deps = -r requirements/typing.txt
commands = mypy
[testenv:docs]
+package = wheel
+wheel_build_env = .pkg
deps = -r requirements/docs.txt
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs {envtmpdir}/html