Search code examples
pythonflaskflask-sqlalchemy

RuntimeError: A 'SQLAlchemy' instance has already been registered on this Flask app


I am writing a test for my Flask app that uses Flask-SQLAlchemy. In models.py, I used db = SQLAlchemy(), and wrote a function to configure it with the app. But when I run my test, I get the error "RuntimeError: A 'SQLAlchemy' instance has already been registered on this Flask app". I'm not sure where the test file is creating a new instance of SQLAlchemy.

# flaskr.py
from flask import Flask
from models import setup_db

def create_app(test_config=None):
    app = Flask(__name__)
    setup_db(app)
    return app
# models.py
from flask_sqlalchemy import SQLAlchemy

database_path = "postgresql://student:student@localhost/bookshelf"

db = SQLAlchemy()

def setup_db(app, database_path=database_path):
    app.config["SQLALCHEMY_DATABASE_URI"] = database_path
    db.init_app(app)

    with app.app_context():
        db.create_all()
# test_flaskr.py
import unittest

from flaskr import create_app
from models import setup_db

class BookTestCase(unittest.TestCase):
    def setUp(self):
        self.app = create_app()
        self.client = self.app.test_client()
        setup_db(self.app, "postgresql://student:student@localhost/bookshelf_test")

        with self.app.app_context():
            self.db = SQLAlchemy()
            self.db.init_app(self.app)
            self.db.create_all()

    def test_get_paginated_books(self):
        res = self.client.get("/books")
        data = res.json
        self.assertEqual(res.status_code, 200)
        self.assertTrue(data["success"])
        self.assertTrue(data["total_books"])
        self.assertTrue(len(data["books"]))

When I run the test, I get the following error:

$ python -m unittest -v test_flaskr.py

test_get_paginated_books (test_flaskr.BookTestCase) ... ERROR

======================================================================
ERROR: test_get_paginated_books (test_flaskr.BookTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):      
  File "C:\Users\swilk\OneDrive\DOCS-Programming\udacity-demo-bookshelf\backend\test_flaskr.py", line 21, in setUp      
    setup_db(self.app, self.database_path)
  File "C:\Users\swilk\OneDrive\DOCS-Programming\udacity-demo-bookshelf\backend\models.py", line 24, in setup_db        
    db.init_app(app)
  File "C:\Users\swilk\OneDrive\DOCS-Programming\udacity-demo-bookshelf\env\lib\site-packages\flask_sqlalchemy\extension.py", line 253, in init_app
    raise RuntimeError(
RuntimeError: A 'SQLAlchemy' instance has already been registered on this Flask app. Import and use that instance instead.

----------------------------------------------------------------------
Ran 1 tests in 0.226s

FAILED (errors=1)

Solution

  • Flask-SQLAlchemy 3 raises an error for a common incorrect setup of the extension. You must only call db.init_app(app) once for a given pair of db and app instances.

    You defined db = SQLAlchemy() in models, then called init_app on it in create_app. You must use that db, not create another instance. You must not call db.init_app again, it was already called in create_app.

    Your test's setUp is calling create_app, but then it's also calling setup_db again even though that's already called as part of create_app. Then further down, you're creating a new self.db = SQLAlchemy() and calling init_app on it with the same app you've already set up the other instance on. This is all incorrect.

    It looks like you're doing this because you're trying to set a database configuration for the test. As explained in the Flask tutorial, that's what the create_app function's test_config parameter is for. Get rid of the setup_db function, it's unnecessary indirection. Pass your test configuration to the factory to override default config.

    def create_app(test_config=None):
        app = Flask(__name__)
        app.config.from_mapping(
            SQLALCHEMY_DATABASE_URI="postgresql://student:student@localhost/bookshelf"
        )
        
        if test_config is not None:
            app.config.from_mapping(test_config)
    
        db.init_app(app)
    
        with app.app_context():
            db.create_all()
    
        return app
    
    from flaskr import create_app, db
    
    class BookTestCase(TestCase):
        def setUp(self):
            self.app = create_app({
                "SQLALCHEMY_DATABASE_URI": "postgresql://student:student@localhost/bookshelf_test"
            })
    

    Note that nothing was done directly with db in setUp, it was only imported to be used during tests.