Does db.session.commit change app context in Flask-SQLAlchemy?

Viewed 1949

I am configuring a fixture for pytest that creates a flask app instance. My app is created with Application Factories pattern. I am at the stage of connecting it to the database and struggle to understand the difference between 2 patterns.

# project/__init__.py
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy


db = SQLAlchemy()


def create_app():
    app = Flask(__name__)

    app_settings = os.getenv('APP_SETTINGS')
    app.config.from_object(app_settings)

    db.init_app(app)

    [blueprint code]

    return app

In my fixture, I think I understand the need for:

  • db.create_all() during setup: create my tables
  • db.drop_all() during teardown: clean database after tests
  • db.session.remove() during teardown: avoid some weird locks on postgres when hitting the database frequently in tests

The first setup (inspired by Miguel Grinberg book) makes sense to me:

import pytest
from project import create_app, db


@pytest.fixture
def app():
    app = create_app()
    with app.app_context():
        db.create_all()
        yield app
        db.session.remove()
        db.drop_all()

It also matches the behavior I get in an interactive session, where I need to activate/push the app_context to bind the database:

Python 3.6.1 (default, Jun 21 2017, 18:45:41) 
[GCC 4.9.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from project import create_app, db
>>> app = create_app()
>>> db
<SQLAlchemy engine=None>
>>> app_ctx = app.app_context()
>>> app_ctx.push()
>>> db.create_all()
>>> db
<SQLAlchemy engine='postgres://postgres:postgres@users-db:5432/users_dev'>

The second setup (inspired by testdriven.io) also work in pytest but I don't know why:

import pytest
from project import create_app, db


@pytest.fixture
def app():
    app = create_app()
    db.create_all()
    db.session.commit()  # fail when this is removed
    yield app
    db.session.remove()
    db.drop_all()

Actually, if I try to do the same in an interactive session, I get an error:

Python 3.6.1 (default, Jun 21 2017, 18:45:41) 
[GCC 4.9.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from project import create_app, db
>>> app = create_app()
>>> db.create_all()
[...] timeError: application not registered on db instance and no application bound to current context

I tried to run the fixture without db.session.commit(), thinking that maybe I was in the app context by default (similar to what I do with with app_context() in the first fixture). But it fails if I remove it.

1 Answers
Related