Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested blueprint registration causes error when using test-scoped app fixtures #4786

Closed
tachyondecay opened this issue Aug 25, 2022 · 5 comments
Milestone

Comments

@tachyondecay
Copy link

For apps that use nested blueprints, the way that Flask currently tracks how child blueprints have been registered causes both an error and a warning (that will soon be an error) when creating multiple app objects. This is a problem when testing.

For the below code, test_2 will fail because child has already been registered on parent, resulting in

ValueError: The name 'child' is already registered for this blueprint 'parent.child'. Use 'name=' to provide a unique name.

Additionally, the second call to parent.register_blueprint() emits a warning:

UserWarning: The setup method 'register_blueprint' can no longer be called on the blueprint 'parent'. It has already been registered at least once, any changes will not be applied consistently.
  Make sure all imports, decorators, functions, etc. needed to set up the blueprint are done before registering it.
  This warning will become an exception in Flask 2.3.
    parent.register_blueprint(child, url_prefix="/child")
import pytest

from flask import Blueprint, Flask

parent = Blueprint("parent", __name__)
child = Blueprint("child", __name__)


def create_app():
    app = Flask(__name__)

    parent.register_blueprint(child, url_prefix="/child")
    app.register_blueprint(parent, url_prefix="/parent")

    return app


@pytest.fixture
def app():
    app = create_app()
    yield app


def test_1(app):
    pass


def test_2(app):
    pass

User workarounds:

  • Session-scope the app fixture — this is what I'm currently doing but creates issues with test isolation in FlaskLoginClient from Flask-Login (but I worked around that too)
  • Create the blueprint objects inside create_app() — this isn't ideal because it makes registering routes on the blueprints more complex
  • Alter the parent blueprint's private attrs in the app fixture — adding these lines after the yield statement also fixes the problem for a given parent blueprint but is not a very flexible solution overall:
parent._blueprints = []
parent._got_registered_once = False

Environment:

  • Python version: 3.8.10
  • Flask version: 2.2.2
@davidism davidism added this to the 2.2.3 milestone Sep 3, 2022
@rasimandiran
Copy link

What should be the expected behavior? Causes a warning (will soon be an error) looks like correct behavior. Do you think that there should be a method that makes these operations:

parent._blueprints = []
parent._got_registered_once = False

Maybe something like:

parent.reset_blueprint()

Or any another suggestion?

Thanks.

@j0eTheRipper
Copy link

Hi
what do you mean by session scope the fixtures?
please show an example

@tachyondecay
Copy link
Author

Hi what do you mean by session scope the fixtures? please show an example

I've replied to you on Discord. Please limit discussion here to the issue itself and ask side questions there.

cjmayo added a commit to cjmayo/flask-admin that referenced this issue Jan 24, 2023
AssertionError: The setup method 'register_blueprint' can no longer be
called on the application.

pallets/flask#4786
cjmayo added a commit to cjmayo/flask-admin that referenced this issue Jan 24, 2023
AssertionError: The setup method 'register_blueprint' can no longer be
called on the application.

pallets/flask#4786
cjmayo added a commit to cjmayo/flask-admin that referenced this issue Jan 24, 2023
AssertionError: The setup method 'register_blueprint' can no longer be
called on the application.

pallets/flask#4786
@ChandanChainani
Copy link

@tachyondecay

The problem can be solve with a single line change which is causing the issue

import pytest

from flask import Blueprint, Flask

parent = Blueprint("parent", __name__)
child = Blueprint("child", __name__)


def create_app():
    app = Flask(__name__)

    parent.register_blueprint(child, url_prefix="/child")        <--- Cause of issue
    app.register_blueprint(parent, url_prefix="/parent")

    return app


@pytest.fixture
def app():
    app = create_app()
    yield app


def test_1(app):
    pass


def test_2(app):
    pass

The highlighted part is causing the issue since we are defining parent blueprint instance as global and registering child blueprint inside function so when test_1 is run it call create_app which register child over parent blueprint so it works perfectly but when test_2 call create_app again it tries to again register child blueprint to parent blueprint since it already registered it cause issue and error is thrown

To solve the problem we simple have to move the child blueprint registration to parent outside create_app function.

import pytest

from flask import Blueprint, Flask

parent = Blueprint("parent", __name__)
child = Blueprint("child", __name__)
parent.register_blueprint(child, url_prefix="/child")


def create_app():
    app = Flask(__name__)

    app.register_blueprint(parent, url_prefix="/parent")

    return app


@pytest.fixture
def app():
    app = create_app()
    yield app


def test_1(app):
    pass


def test_2(app):
    pass

@davidism
Copy link
Member

I think the problem here is illustrated by @ChandanChainani's comment/solution. Blueprints are global objects, you're mutating the same object each time you call create_app. In contrast, you're creating a new Flask instance inside that. If you were to define app outside the factory, you'd observe the same issue.

If the blueprints are always registered in the same way, you should move them outside the factory. If they change during setup, then maybe you want to create the "top" blueprint each time, like the Flask instance, then register the child blueprints before registering it on the app.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 2, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants