diff --git a/CHANGES.rst b/CHANGES.rst index 3ce9699e..d5fa9fe9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -20,6 +20,8 @@ Unreleased registering the blueprint will show a warning. In the next version, this will become an error just like the application setup methods. :issue:`4571` +- ``before_first_request`` is deprecated. Run setup code when creating + the application instead. :issue:`4605` Version 2.1.3 @@ -1032,8 +1034,7 @@ Released 2011-09-29, codename Rakija earlier feedback when users forget to import view code ahead of time. - Added the ability to register callbacks that are only triggered once - at the beginning of the first request. - (:meth:`Flask.before_first_request`) + at the beginning of the first request. (``before_first_request``) - Malformed JSON data will now trigger a bad request HTTP exception instead of a value error which usually would result in a 500 internal server error if not handled. This is a backwards diff --git a/src/flask/app.py b/src/flask/app.py index 93335029..65e95623 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -451,6 +451,10 @@ class Flask(Scaffold): #: first request to this instance. To register a function, use the #: :meth:`before_first_request` decorator. #: + #: .. deprecated:: 2.2 + #: Will be removed in Flask 2.3. Run setup code when + #: creating the application instead. + #: #: .. versionadded:: 0.8 self.before_first_request_funcs: t.List[ft.BeforeFirstRequestCallable] = [] @@ -1255,8 +1259,21 @@ class Flask(Scaffold): The function will be called without any arguments and its return value is ignored. + .. deprecated:: 2.2 + Will be removed in Flask 2.3. Run setup code when creating + the application instead. + .. versionadded:: 0.8 """ + import warnings + + warnings.warn( + "'before_first_request' is deprecated and will be removed" + " in Flask 2.3. Run setup code while creating the" + " application instead.", + DeprecationWarning, + stacklevel=2, + ) self.before_first_request_funcs.append(f) return f @@ -1552,7 +1569,17 @@ class Flask(Scaffold): .. versionadded:: 0.7 """ - self.try_trigger_before_first_request_functions() + # Run before_first_request functions if this is the thread's first request. + # Inlined to avoid a method call on subsequent requests. + # This is deprecated, will be removed in Flask 2.3. + if not self._got_first_request: + with self._before_request_lock: + if not self._got_first_request: + for func in self.before_first_request_funcs: + self.ensure_sync(func)() + + self._got_first_request = True + try: request_started.send(self) rv = self.preprocess_request() @@ -1591,22 +1618,6 @@ class Flask(Scaffold): ) return response - def try_trigger_before_first_request_functions(self) -> None: - """Called before each request and will ensure that it triggers - the :attr:`before_first_request_funcs` and only exactly once per - application instance (which means process usually). - - :internal: - """ - if self._got_first_request: - return - with self._before_request_lock: - if self._got_first_request: - return - for func in self.before_first_request_funcs: - self.ensure_sync(func)() - self._got_first_request = True - def make_default_options_response(self) -> Response: """This method is called to create the default ``OPTIONS`` response. This can be changed through subclassing to change the default diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index 7ed599a0..76b36067 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -543,7 +543,20 @@ class Blueprint(Scaffold): ) -> ft.BeforeFirstRequestCallable: """Like :meth:`Flask.before_first_request`. Such a function is executed before the first request to the application. + + .. deprecated:: 2.2 + Will be removed in Flask 2.3. Run setup code when creating + the application instead. """ + import warnings + + warnings.warn( + "'before_app_first_request' is deprecated and will be" + " removed in Flask 2.3. Use 'record_once' instead to run" + " setup code when registering the blueprint.", + DeprecationWarning, + stacklevel=2, + ) self.record_once(lambda s: s.app.before_first_request_funcs.append(f)) return f diff --git a/tests/test_async.py b/tests/test_async.py index c8bb9bf0..38be27c9 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -107,10 +107,12 @@ def test_async_before_after_request(): def index(): return "" - @app.before_first_request - async def before_first(): - nonlocal app_first_called - app_first_called = True + with pytest.deprecated_call(): + + @app.before_first_request + async def before_first(): + nonlocal app_first_called + app_first_called = True @app.before_request async def before(): diff --git a/tests/test_basic.py b/tests/test_basic.py index fa9f902d..7c1f4197 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1684,9 +1684,11 @@ def test_no_setup_after_first_request(app, client): def test_before_first_request_functions(app, client): got = [] - @app.before_first_request - def foo(): - got.append(42) + with pytest.deprecated_call(): + + @app.before_first_request + def foo(): + got.append(42) client.get("/") assert got == [42] @@ -1698,10 +1700,12 @@ def test_before_first_request_functions(app, client): def test_before_first_request_functions_concurrent(app, client): got = [] - @app.before_first_request - def foo(): - time.sleep(0.2) - got.append(42) + with pytest.deprecated_call(): + + @app.before_first_request + def foo(): + time.sleep(0.2) + got.append(42) def get_and_assert(): client.get("/") diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index fbe9eeee..1bf130f5 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -722,9 +722,11 @@ def test_app_request_processing(app, client): bp = flask.Blueprint("bp", __name__) evts = [] - @bp.before_app_first_request - def before_first_request(): - evts.append("first") + with pytest.deprecated_call(): + + @bp.before_app_first_request + def before_first_request(): + evts.append("first") @bp.before_app_request def before_app():