blinker is required, signals are always available

This commit is contained in:
David Lord 2023-04-13 07:34:14 -07:00
parent e1e4e82096
commit 9cb1a7a52d
No known key found for this signature in database
GPG Key ID: 7A1C87E3F5BC42A8
18 changed files with 77 additions and 163 deletions

View File

@ -28,6 +28,8 @@ Unreleased
- The ``app.got_first_request`` property is deprecated. :pr:`4997` - The ``app.got_first_request`` property is deprecated. :pr:`4997`
- The ``locked_cached_property`` decorator is deprecated. Use a lock inside the - The ``locked_cached_property`` decorator is deprecated. Use a lock inside the
decorated function if locking is needed. :issue:`4993` decorated function if locking is needed. :issue:`4993`
- Signals are always available. ``blinker>=1.6.2`` is a required dependency. The
``signals_available`` attribute is deprecated. :issue:`5056`
- Remove uses of locks that could cause requests to block each other very briefly. - Remove uses of locks that could cause requests to block each other very briefly.
:issue:`4993` :issue:`4993`
- Use modern packaging metadata with ``pyproject.toml`` instead of ``setup.cfg``. - Use modern packaging metadata with ``pyproject.toml`` instead of ``setup.cfg``.

View File

@ -333,14 +333,9 @@ Useful Internals
Signals Signals
------- -------
.. versionadded:: 0.6 Signals are provided by the `Blinker`_ library. See :doc:`signals` for an introduction.
.. data:: signals.signals_available .. _blinker: https://blinker.readthedocs.io/
``True`` if the signaling system is available. This is the case
when `blinker`_ is installed.
The following signals exist in Flask:
.. data:: template_rendered .. data:: template_rendered
@ -507,7 +502,6 @@ The following signals exist in Flask:
.. versionadded:: 0.10 .. versionadded:: 0.10
.. data:: message_flashed .. data:: message_flashed
This signal is sent when the application is flashing a message. The This signal is sent when the application is flashing a message. The
@ -525,22 +519,10 @@ The following signals exist in Flask:
.. versionadded:: 0.10 .. versionadded:: 0.10
.. class:: signals.Namespace .. data:: signals.signals_available
An alias for :class:`blinker.base.Namespace` if blinker is available, .. deprecated:: 2.3
otherwise a dummy class that creates fake signals. This class is Will be removed in Flask 2.4. Signals are always available
available for Flask extensions that want to provide the same fallback
system as Flask itself.
.. method:: signal(name, doc=None)
Creates a new signal for this namespace if blinker is available,
otherwise returns a fake signal that has a send method that will
do nothing but will fail with a :exc:`RuntimeError` for all other
operations, including connecting.
.. _blinker: https://pypi.org/project/blinker/
Class-Based Views Class-Based Views

View File

@ -140,10 +140,8 @@ Accessing ``db`` will call ``get_db`` internally, in the same way that
Events and Signals Events and Signals
------------------ ------------------
The application will call functions registered with The application will call functions registered with :meth:`~Flask.teardown_appcontext`
:meth:`~Flask.teardown_appcontext` when the application context is when the application context is popped.
popped.
If :data:`~signals.signals_available` is true, the following signals are The following signals are sent: :data:`appcontext_pushed`,
sent: :data:`appcontext_pushed`, :data:`appcontext_tearing_down`, and :data:`appcontext_tearing_down`, and :data:`appcontext_popped`.
:data:`appcontext_popped`.

View File

@ -24,12 +24,14 @@ These distributions will be installed automatically when installing Flask.
to protect Flask's session cookie. to protect Flask's session cookie.
* `Click`_ is a framework for writing command line applications. It provides * `Click`_ is a framework for writing command line applications. It provides
the ``flask`` command and allows adding custom management commands. the ``flask`` command and allows adding custom management commands.
* `Blinker`_ provides support for :doc:`signals`.
.. _Werkzeug: https://palletsprojects.com/p/werkzeug/ .. _Werkzeug: https://palletsprojects.com/p/werkzeug/
.. _Jinja: https://palletsprojects.com/p/jinja/ .. _Jinja: https://palletsprojects.com/p/jinja/
.. _MarkupSafe: https://palletsprojects.com/p/markupsafe/ .. _MarkupSafe: https://palletsprojects.com/p/markupsafe/
.. _ItsDangerous: https://palletsprojects.com/p/itsdangerous/ .. _ItsDangerous: https://palletsprojects.com/p/itsdangerous/
.. _Click: https://palletsprojects.com/p/click/ .. _Click: https://palletsprojects.com/p/click/
.. _Blinker: https://blinker.readthedocs.io/
Optional dependencies Optional dependencies
@ -38,13 +40,11 @@ Optional dependencies
These distributions will not be installed automatically. Flask will detect and These distributions will not be installed automatically. Flask will detect and
use them if you install them. use them if you install them.
* `Blinker`_ provides support for :doc:`signals`.
* `python-dotenv`_ enables support for :ref:`dotenv` when running ``flask`` * `python-dotenv`_ enables support for :ref:`dotenv` when running ``flask``
commands. commands.
* `Watchdog`_ provides a faster, more efficient reloader for the development * `Watchdog`_ provides a faster, more efficient reloader for the development
server. server.
.. _Blinker: https://blinker.readthedocs.io/en/stable/
.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
.. _watchdog: https://pythonhosted.org/watchdog/ .. _watchdog: https://pythonhosted.org/watchdog/

View File

@ -204,21 +204,16 @@ contexts until the ``with`` block exits.
Signals Signals
~~~~~~~ ~~~~~~~
If :data:`~signals.signals_available` is true, the following signals are The following signals are sent:
sent:
#. :data:`request_started` is sent before the #. :data:`request_started` is sent before the :meth:`~Flask.before_request` functions
:meth:`~Flask.before_request` functions are called. are called.
#. :data:`request_finished` is sent after the :meth:`~Flask.after_request` functions
#. :data:`request_finished` is sent after the are called.
:meth:`~Flask.after_request` functions are called. #. :data:`got_request_exception` is sent when an exception begins to be handled, but
before an :meth:`~Flask.errorhandler` is looked up or called.
#. :data:`got_request_exception` is sent when an exception begins to #. :data:`request_tearing_down` is sent after the :meth:`~Flask.teardown_request`
be handled, but before an :meth:`~Flask.errorhandler` is looked up or functions are called.
called.
#. :data:`request_tearing_down` is sent after the
:meth:`~Flask.teardown_request` functions are called.
.. _notes-on-proxies: .. _notes-on-proxies:

View File

@ -1,33 +1,28 @@
Signals Signals
======= =======
.. versionadded:: 0.6 Signals are a lightweight way to notify subscribers of certain events during the
lifecycle of the application and each request. When an event occurs, it emits the
signal, which calls each subscriber.
Starting with Flask 0.6, there is integrated support for signalling in Signals are implemented by the `Blinker`_ library. See its documentation for detailed
Flask. This support is provided by the excellent `blinker`_ library and information. Flask provides some built-in signals. Extensions may provide their own.
will gracefully fall back if it is not available.
What are signals? Signals help you decouple applications by sending Many signals mirror Flask's decorator-based callbacks with similar names. For example,
notifications when actions occur elsewhere in the core framework or the :data:`.request_started` signal is similar to the :meth:`~.Flask.before_request`
another Flask extensions. In short, signals allow certain senders to decorator. The advantage of signals over handlers is that they can be subscribed to
notify subscribers that something happened. temporarily, and can't directly affect the application. This is useful for testing,
metrics, auditing, and more. For example, if you want to know what templates were
rendered at what parts of what requests, there is a signal that will notify you of that
information.
Flask comes with a couple of signals and other extensions might provide
more. Also keep in mind that signals are intended to notify subscribers
and should not encourage subscribers to modify data. You will notice that
there are signals that appear to do the same thing like some of the
builtin decorators do (eg: :data:`~flask.request_started` is very similar
to :meth:`~flask.Flask.before_request`). However, there are differences in
how they work. The core :meth:`~flask.Flask.before_request` handler, for
example, is executed in a specific order and is able to abort the request
early by returning a response. In contrast all signal handlers are
executed in undefined order and do not modify any data.
The big advantage of signals over handlers is that you can safely Core Signals
subscribe to them for just a split second. These temporary ------------
subscriptions are helpful for unit testing for example. Say you want to
know what templates were rendered as part of a request: signals allow you See :ref:`core-signals-list` for a list of all built-in signals. The :doc:`lifecycle`
to do exactly that. page also describes the order that signals and decorators execute.
Subscribing to Signals Subscribing to Signals
---------------------- ----------------------
@ -99,11 +94,6 @@ The example above would then look like this::
... ...
template, context = templates[0] template, context = templates[0]
.. admonition:: Blinker API Changes
The :meth:`~blinker.base.Signal.connected_to` method arrived in Blinker
with version 1.1.
Creating Signals Creating Signals
---------------- ----------------
@ -123,12 +113,6 @@ The name for the signal here makes it unique and also simplifies
debugging. You can access the name of the signal with the debugging. You can access the name of the signal with the
:attr:`~blinker.base.NamedSignal.name` attribute. :attr:`~blinker.base.NamedSignal.name` attribute.
.. admonition:: For Extension Developers
If you are writing a Flask extension and you want to gracefully degrade for
missing blinker installations, you can do so by using the
:class:`flask.signals.Namespace` class.
.. _signals-sending: .. _signals-sending:
Sending Signals Sending Signals
@ -170,7 +154,7 @@ in :ref:`signals-sending` and the :data:`~flask.request_tearing_down` signal.
Decorator Based Signal Subscriptions Decorator Based Signal Subscriptions
------------------------------------ ------------------------------------
With Blinker 1.1 you can also easily subscribe to signals by using the new You can also easily subscribe to signals by using the
:meth:`~blinker.base.NamedSignal.connect_via` decorator:: :meth:`~blinker.base.NamedSignal.connect_via` decorator::
from flask import template_rendered from flask import template_rendered
@ -179,10 +163,5 @@ With Blinker 1.1 you can also easily subscribe to signals by using the new
def when_template_rendered(sender, template, context, **extra): def when_template_rendered(sender, template, context, **extra):
print(f'Template {template.name} is rendered with {context}') print(f'Template {template.name} is rendered with {context}')
Core Signals
------------
Take a look at :ref:`core-signals-list` for a list of all builtin signals.
.. _blinker: https://pypi.org/project/blinker/ .. _blinker: https://pypi.org/project/blinker/

View File

@ -11,7 +11,7 @@ dependencies = ["flask"]
Documentation = "https://flask.palletsprojects.com/patterns/jquery/" Documentation = "https://flask.palletsprojects.com/patterns/jquery/"
[project.optional-dependencies] [project.optional-dependencies]
test = ["pytest", "blinker"] test = ["pytest"]
[build-system] [build-system]
requires = ["setuptools"] requires = ["setuptools"]

View File

@ -24,6 +24,7 @@ dependencies = [
"Jinja2>=3.0", "Jinja2>=3.0",
"itsdangerous>=2.0", "itsdangerous>=2.0",
"click>=8.0", "click>=8.0",
"blinker>=1.6.2",
"importlib-metadata>=3.6.0; python_version < '3.10'", "importlib-metadata>=3.6.0; python_version < '3.10'",
] ]
dynamic = ["version"] dynamic = ["version"]
@ -90,7 +91,6 @@ warn_unused_ignores = true
[[tool.mypy.overrides]] [[tool.mypy.overrides]]
module = [ module = [
"asgiref.*", "asgiref.*",
"blinker.*",
"dotenv.*", "dotenv.*",
"cryptography.*", "cryptography.*",
"importlib_metadata", "importlib_metadata",

View File

@ -11,5 +11,3 @@ packaging==23.0
# via build # via build
pyproject-hooks==1.0.0 pyproject-hooks==1.0.0
# via build # via build
tomli==2.0.1
# via build

View File

@ -3,3 +3,4 @@ Jinja2==3.0.0
MarkupSafe==2.0.0 MarkupSafe==2.0.0
itsdangerous==2.0.0 itsdangerous==2.0.0
click==8.0.0 click==8.0.0
blinker==1.6.2

View File

@ -1,10 +1,12 @@
# SHA1:4de7d9e6254a945fd97ec10880dd23b6cd43b70d # SHA1:575f86f45391b662630a6080f0a12676215eb0cf
# #
# This file is autogenerated by pip-compile-multi # This file is autogenerated by pip-compile-multi
# To update, run: # To update, run:
# #
# pip-compile-multi # pip-compile-multi
# #
blinker==1.6.2
# via -r requirements/tests-pallets-min.in
click==8.0.0 click==8.0.0
# via -r requirements/tests-pallets-min.in # via -r requirements/tests-pallets-min.in
itsdangerous==2.0.0 itsdangerous==2.0.0

View File

@ -1,5 +1,4 @@
pytest pytest
asgiref asgiref
blinker
greenlet ; python_version < "3.11" greenlet ; python_version < "3.11"
python-dotenv>=1; python_version >= "3.8" python-dotenv>=1; python_version >= "3.8"

View File

@ -1,4 +1,4 @@
# SHA1:30698f5f4f9cba5088318306829a15b0dc123b38 # SHA1:3c8dde35aba20388b22430b17974af8ef8205b4f
# #
# This file is autogenerated by pip-compile-multi # This file is autogenerated by pip-compile-multi
# To update, run: # To update, run:
@ -7,12 +7,6 @@
# #
asgiref==3.6.0 asgiref==3.6.0
# via -r requirements/tests.in # via -r requirements/tests.in
blinker==1.6.1
# via -r requirements/tests.in
exceptiongroup==1.1.1
# via pytest
greenlet==2.0.2 ; python_version < "3.11"
# via -r requirements/tests.in
iniconfig==2.0.0 iniconfig==2.0.0
# via pytest # via pytest
packaging==23.0 packaging==23.0
@ -23,7 +17,3 @@ pytest==7.3.0
# via -r requirements/tests.in # via -r requirements/tests.in
python-dotenv==1.0.0 ; python_version >= "3.8" python-dotenv==1.0.0 ; python_version >= "3.8"
# via -r requirements/tests.in # via -r requirements/tests.in
tomli==2.0.1
# via pytest
typing-extensions==4.5.0
# via blinker

View File

@ -15,8 +15,6 @@ mypy-extensions==1.0.0
# via mypy # via mypy
pycparser==2.21 pycparser==2.21
# via cffi # via cffi
tomli==2.0.1
# via mypy
types-contextvars==2.4.7.2 types-contextvars==2.4.7.2
# via -r requirements/typing.in # via -r requirements/typing.in
types-dataclasses==0.6.6 types-dataclasses==0.6.6

View File

@ -32,7 +32,6 @@ from .signals import message_flashed as message_flashed
from .signals import request_finished as request_finished from .signals import request_finished as request_finished
from .signals import request_started as request_started from .signals import request_started as request_started
from .signals import request_tearing_down as request_tearing_down from .signals import request_tearing_down as request_tearing_down
from .signals import signals_available as signals_available
from .signals import template_rendered as template_rendered from .signals import template_rendered as template_rendered
from .templating import render_template as render_template from .templating import render_template as render_template
from .templating import render_template_string as render_template_string from .templating import render_template_string as render_template_string
@ -89,4 +88,15 @@ def __getattr__(name):
) )
return Markup return Markup
if name == "signals_available":
import warnings
warnings.warn(
"'signals_available' is deprecated and will be removed in Flask 2.4."
" Signals are always available",
DeprecationWarning,
stacklevel=2,
)
return True
raise AttributeError(name) raise AttributeError(name)

View File

@ -1,49 +1,13 @@
from __future__ import annotations
import typing as t import typing as t
import warnings
try: from blinker import Namespace
from blinker import Namespace
signals_available = True # This namespace is only for signals provided by Flask itself.
except ImportError:
signals_available = False
class Namespace: # type: ignore
def signal(self, name: str, doc: t.Optional[str] = None) -> "_FakeSignal":
return _FakeSignal(name, doc)
class _FakeSignal:
"""If blinker is unavailable, create a fake class with the same
interface that allows sending of signals but will fail with an
error on anything else. Instead of doing anything on send, it
will just ignore the arguments and do nothing instead.
"""
def __init__(self, name: str, doc: t.Optional[str] = None) -> None:
self.name = name
self.__doc__ = doc
def send(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
pass
def _fail(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
raise RuntimeError(
"Signalling support is unavailable because the blinker"
" library is not installed."
) from None
connect = connect_via = connected_to = temporarily_connected_to = _fail
disconnect = _fail
has_receivers_for = receivers_for = _fail
del _fail
# The namespace for code signals. If you are not Flask code, do
# not put signals in here. Create your own namespace instead.
_signals = Namespace() _signals = Namespace()
# Core signals. For usage examples grep the source code or consult
# the API documentation in docs/api.rst as well as docs/signals.rst
template_rendered = _signals.signal("template-rendered") template_rendered = _signals.signal("template-rendered")
before_render_template = _signals.signal("before-render-template") before_render_template = _signals.signal("before-render-template")
request_started = _signals.signal("request-started") request_started = _signals.signal("request-started")
@ -54,3 +18,16 @@ appcontext_tearing_down = _signals.signal("appcontext-tearing-down")
appcontext_pushed = _signals.signal("appcontext-pushed") appcontext_pushed = _signals.signal("appcontext-pushed")
appcontext_popped = _signals.signal("appcontext-popped") appcontext_popped = _signals.signal("appcontext-popped")
message_flashed = _signals.signal("message-flashed") message_flashed = _signals.signal("message-flashed")
def __getattr__(name: str) -> t.Any:
if name == "signals_available":
warnings.warn(
"The 'signals_available' attribute is deprecated and will be removed in"
" Flask 2.4. Signals are always available.",
DeprecationWarning,
stacklevel=2,
)
return True
raise AttributeError(name)

View File

@ -1,16 +1,5 @@
import pytest
try:
import blinker
except ImportError:
blinker = None
import flask import flask
pytestmark = pytest.mark.skipif(
blinker is None, reason="Signals require the blinker library."
)
def test_template_rendered(app, client): def test_template_rendered(app, client):
@app.route("/") @app.route("/")

View File

@ -10,11 +10,6 @@ from flask.json import jsonify
from flask.testing import EnvironBuilder from flask.testing import EnvironBuilder
from flask.testing import FlaskCliRunner from flask.testing import FlaskCliRunner
try:
import blinker
except ImportError:
blinker = None
def test_environ_defaults_from_config(app, client): def test_environ_defaults_from_config(app, client):
app.config["SERVER_NAME"] = "example.com:1234" app.config["SERVER_NAME"] = "example.com:1234"
@ -285,7 +280,6 @@ def test_json_request_and_response(app, client):
assert rv.get_json() == json_data assert rv.get_json() == json_data
@pytest.mark.skipif(blinker is None, reason="blinker is not installed")
def test_client_json_no_app_context(app, client): def test_client_json_no_app_context(app, client):
@app.route("/hello", methods=["POST"]) @app.route("/hello", methods=["POST"])
def hello(): def hello():