ProfCalculus
ProfCalculus

Reputation: 15

flask_sqlalchemy create_all() fails silently in unit testing

I'm writing unit tests for a REST API written in Flask with the flask_sqlalchemy extension. Because I have a number of model classes, I wrote a TestCase subclass to do the standard setUp/cleanUp of the test database. All my test classes inherit from this. Each test succeeds when run alone, but when I run more than one test in a single class, the second setUp() fails on the self.db.session.commit() (I'm trying to add an entry to the User table) because self.db.create_all() has (silently) failed to create any tables. Here is my base test class, in the __init__.py of the test package:

import unittest

from .test_client import TestClient
from .. import create_app
from pdb import set_trace as DBG

class ApiTest(unittest.TestCase):
    default_username = 'fred'
    default_password = 'bloggs'
    db = None

    def setUp(self):
        try:
            self.app = create_app('testing')
            self.addCleanup(self.cleanUp)
            self.ctx = self.app.app_context()
            self.ctx.push()
            from .. import db
            self.db = db
            self.db.session.commit()
            self.db.drop_all(app=self.app)
            from ..models import User, Player, Team, Match, Game
            # self.app.logger.debug('drop_all())')
            self.db.create_all(app=self.app)
            # self.app.logger.debug('create_all())')
            user = User(user_name=self.default_username)
            user.password = self.default_password
            self.db.session.add(u)
            self.db.session.commit()
            self.client = TestClient(self.app, user.generate_auth_token(), '')
        except Exception, ex:
            self.app.logger.error("Error during setUp: %s" % ex)
            raise

    def cleanUp(self):
        try:
            self.db.session.commit()
            self.db.session.remove()
            self.db.drop_all(app=self.app)
            # self.app.logger.debug('drop_all())')
            self.ctx.pop()
        except Exception, ex:
            self.app.logger.error("Error during cleanUp: %s" % ex)
            raise

Can anyone tell me what's wrong here please?

EDIT: Added the code for create_app() as requested.

# chessleague/__init__.py
import os

from flask import Flask, g
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager


from . import config


app = None
db = None  # The database, initialised in create_app()


def create_app(config_name):
    app = Flask(__name__)
    app.config.update(config.get_config(config_name))
    # if app.config['USE_TOKEN_AUTH']:
    #     from api.token import token as token_blueprint
    #     app.register_blueprint(token_blueprint, url_prefix='/auth')
    import logging
    from logging.handlers import SysLogHandler
    syslog_handler = SysLogHandler()
    syslog_handler.setLevel(logging.WARNING)
    app.logger.addHandler(syslog_handler)

    login_manager = LoginManager()
    login_manager.login_view = 'auth.login'
    login_manager.init_app(app)
    global db
    db = SQLAlchemy(app)
    db.init_app(app)
    from .models import User,Player,Game,Match,Team,Post
    db.create_all()
    from .api import api as api_blueprint
    app.register_blueprint(api_blueprint, url_prefix='/chessleague')
    return app

`

Upvotes: 0

Views: 1483

Answers (2)

Fine
Fine

Reputation: 2154

create_all() applies to the metadata, that is being discovered by importing modules with models. In your case, models' metadata binds to the db from your models.py but you are calling create_all() from chessleague/__init__.db from create_app() function, which is different objects for SqlAlchemy. You can fix that by using db from models.py:

from .models import User,Player,Game,Match,Team,Post, db as models_db
models_db.create_all()

Upvotes: 2

ProfCalculus
ProfCalculus

Reputation: 15

Here's the initialisation sequence that worked for me - comments welcome!

My test class setUp() calls create_app(config_name) from the main app package. The main app package(__init__.py) creates the app instance at module level, ie app=Flask(my_app_package_name) Then my function create_app(config_name)

  • loads the right config into app.config (including the right SQLACHEMY_DATABASE_URL)
  • imports the model classes and db (as model_db) from models.py

This import creates the symbol db at module level in models.py, followed by the model class definitions:

  # models.py
    from . import app
    db = SQLAlchemy(app)
    ...
    class User(db.Model)
    ... 
    etc

Now everything is set up properly: the symbol 'db' can be imported anywhere from models.py, and I can call db.create_all() successfully from my test setUp().

@Fian, can you post your solution as an answer so I can give you credit?

Upvotes: 0

Related Questions