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 ``locked_cached_property`` decorator is deprecated. Use a lock inside the
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.
:issue:`4993`
- Use modern packaging metadata with ``pyproject.toml`` instead of ``setup.cfg``.

View File

@ -333,14 +333,9 @@ Useful Internals
Signals
-------
.. versionadded:: 0.6
Signals are provided by the `Blinker`_ library. See :doc:`signals` for an introduction.
.. data:: signals.signals_available
``True`` if the signaling system is available. This is the case
when `blinker`_ is installed.
The following signals exist in Flask:
.. _blinker: https://blinker.readthedocs.io/
.. data:: template_rendered
@ -507,7 +502,6 @@ The following signals exist in Flask:
.. versionadded:: 0.10
.. data:: message_flashed
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
.. class:: signals.Namespace
.. data:: signals.signals_available
An alias for :class:`blinker.base.Namespace` if blinker is available,
otherwise a dummy class that creates fake signals. This class is
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/
.. deprecated:: 2.3
Will be removed in Flask 2.4. Signals are always available
Class-Based Views

View File

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

View File

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

View File

@ -204,21 +204,16 @@ contexts until the ``with`` block exits.
Signals
~~~~~~~
If :data:`~signals.signals_available` is true, the following signals are
sent:
The following signals are sent:
#. :data:`request_started` is sent before the
:meth:`~Flask.before_request` functions are called.
#. :data:`request_finished` is sent after the
: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:`request_tearing_down` is sent after the
:meth:`~Flask.teardown_request` functions are called.
#. :data:`request_started` is sent before the :meth:`~Flask.before_request` functions
are called.
#. :data:`request_finished` is sent after the :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:`request_tearing_down` is sent after the :meth:`~Flask.teardown_request`
functions are called.
.. _notes-on-proxies:

View File

@ -1,33 +1,28 @@
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
Flask. This support is provided by the excellent `blinker`_ library and
will gracefully fall back if it is not available.
Signals are implemented by the `Blinker`_ library. See its documentation for detailed
information. Flask provides some built-in signals. Extensions may provide their own.
What are signals? Signals help you decouple applications by sending
notifications when actions occur elsewhere in the core framework or
another Flask extensions. In short, signals allow certain senders to
notify subscribers that something happened.
Many signals mirror Flask's decorator-based callbacks with similar names. For example,
the :data:`.request_started` signal is similar to the :meth:`~.Flask.before_request`
decorator. The advantage of signals over handlers is that they can be subscribed to
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
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
to do exactly that.
Core Signals
------------
See :ref:`core-signals-list` for a list of all built-in signals. The :doc:`lifecycle`
page also describes the order that signals and decorators execute.
Subscribing to Signals
----------------------
@ -99,11 +94,6 @@ The example above would then look like this::
...
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
----------------
@ -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
: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:
Sending Signals
@ -170,7 +154,7 @@ in :ref:`signals-sending` and the :data:`~flask.request_tearing_down` signal.
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::
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):
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/

View File

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

View File

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

View File

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

View File

@ -3,3 +3,4 @@ Jinja2==3.0.0
MarkupSafe==2.0.0
itsdangerous==2.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
# To update, run:
#
# pip-compile-multi
#
blinker==1.6.2
# via -r requirements/tests-pallets-min.in
click==8.0.0
# via -r requirements/tests-pallets-min.in
itsdangerous==2.0.0

View File

@ -1,5 +1,4 @@
pytest
asgiref
blinker
greenlet ; python_version < "3.11"
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
# To update, run:
@ -7,12 +7,6 @@
#
asgiref==3.6.0
# 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
# via pytest
packaging==23.0
@ -23,7 +17,3 @@ pytest==7.3.0
# via -r requirements/tests.in
python-dotenv==1.0.0 ; python_version >= "3.8"
# 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
pycparser==2.21
# via cffi
tomli==2.0.1
# via mypy
types-contextvars==2.4.7.2
# via -r requirements/typing.in
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_started as request_started
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 .templating import render_template as render_template
from .templating import render_template_string as render_template_string
@ -89,4 +88,15 @@ def __getattr__(name):
)
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)

View File

@ -1,49 +1,13 @@
from __future__ import annotations
import typing as t
import warnings
try:
from blinker import Namespace
from blinker import Namespace
signals_available = True
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.
# This namespace is only for signals provided by Flask itself.
_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")
before_render_template = _signals.signal("before-render-template")
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_popped = _signals.signal("appcontext-popped")
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
pytestmark = pytest.mark.skipif(
blinker is None, reason="Signals require the blinker library."
)
def test_template_rendered(app, client):
@app.route("/")

View File

@ -10,11 +10,6 @@ from flask.json import jsonify
from flask.testing import EnvironBuilder
from flask.testing import FlaskCliRunner
try:
import blinker
except ImportError:
blinker = None
def test_environ_defaults_from_config(app, client):
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
@pytest.mark.skipif(blinker is None, reason="blinker is not installed")
def test_client_json_no_app_context(app, client):
@app.route("/hello", methods=["POST"])
def hello():