question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Integration sample with SqlAlchemy ?

See original GitHub issue

Hi how would an integration with sqlalchemy works here ??, especially with the scoped_session

I currently have this conftest.py setup on my flask project:

@pytest.fixture(scope='session')
def app(request):
    """Session-wide test `Flask` application."""
    app = create_app('testing')

    # Establish an application context before running the tests.
    ctx = app.app_context()
    ctx.push()

    def teardown():
        ctx.pop()

    request.addfinalizer(teardown)
    return app


@pytest.fixture(scope='session')
def db(app, request):
    """Session-wide test database."""
    def teardown():
        _db.drop_all()

    _db.app = app
    _db.create_all()

    request.addfinalizer(teardown)
    return _db


@pytest.fixture(scope='function')
def session(db, request):
    """Creates a new database session for a test."""
    # connect to the database
    connection = db.engine.connect()
    # begin a non-ORM transaction
    transaction = connection.begin()

    # bind an individual session to the connection
    options = dict(bind=connection, binds={})
    session = db.create_scoped_session(options=options)

    # overload the default session with the session above
    db.session = session

    def teardown():
        session.close()
        # rollback - everything that happened with the
        # session above (including calls to commit())
        # is rolled back.
        transaction.rollback()
        # return connection to the Engine
        connection.close()

    request.addfinalizer(teardown)
    return session

# basically instead of creating and dropping the database for each test
# i just rollback the transaction.
# this setup works perfectly fine without using factory boy

I’ve been searching around the internet on how to make Factory Boy use the same session fixture that pytest use. And then stumble upon this library, but upon looking into the examples, I cant figure how I or this library would do the same.

Issue Analytics

  • State:closed
  • Created 8 years ago
  • Comments:15 (6 by maintainers)

github_iconTop GitHub Comments

6reactions
revmischacommented, Dec 30, 2019

Here is what ended up working for me with pytest-flask-sqlalchemy:

Session = scoped_session(
    lambda: current_app.extensions["sqlalchemy"].db.session,
    scopefunc=lambda: current_app.extensions["sqlalchemy"].db.session,
)

class BaseFactory(factory.alchemy.SQLAlchemyModelFactory):
    class Meta:
        abstract = True
        sqlalchemy_session = Session

I suggest adding a new subclass of SQLAlchemyModelFactory that contains this setting and plugs automatically into pytest-flask-sqlalchemy without any extra configuration.

3reactions
ghostcommented, Aug 13, 2015

hello @olegpidsadnyi , sorry took me a while to respond, i was hooked up in my day job. But for what it’s worth.

The primary reason behind the whole session fixture is for me to be able to rollback my transaction (even for session.commit() ) for every test and not needing to re-create and drop the whole database every test. For that i followed alex’s blog post, but as mentioned in his blog, it has some caveats, for Flask-SQLAlchemy v2.0 has some issues with it’s SignallingSession, see this stackoverflow post and pull request 168. To resolve this problem I did alex’s suggestion and sub-classed the SignallingSession, see code below:

# database.py

from flask.ext.sqlalchemy import SQLAlchemy, SignallingSession, SessionBase


class SessionWithBinds(SignallingSession):
    """This extends the flask-sqlalchemy signalling session so that we may
    provide our own 'binds' argument.

    See https://github.com/mitsuhiko/flask-sqlalchemy/pull/168
    Also http://stackoverflow.com/a/26624146/2475170

    """
    def __init__(self, db, autocommit=False, autoflush=True, **options):
        #: The application that this session belongs to.
        self.app = db.get_app()
        self._model_changes = {}
        #: A flag that controls whether this session should keep track of
        #: model modifications.  The default value for this attribute
        #: is set from the ``SQLALCHEMY_TRACK_MODIFICATIONS`` config
        #: key.
        self.emit_modification_signals = \
            self.app.config['SQLALCHEMY_TRACK_MODIFICATIONS']
        bind = options.pop('bind', None) or db.engine

        # Our changes to allow a 'binds' argument
        binds = options.pop('binds', None)
        if binds is None:
            binds = db.get_binds(self.app)

        SessionBase.__init__(
            self, autocommit=autocommit, autoflush=autoflush,
            bind=bind, binds=binds, **options
        )


class TestFriendlySQLAlchemy(SQLAlchemy):
    """For overriding create_session to return our own Session class"""
    def create_session(self, options):
        return SessionWithBinds(self, **options)


db = TestFriendlySQLAlchemy()

and then I reference this in my app’s __init__.py

# app/__init__.py
from flask import Flask

from config import config

# instead of using the default `from flask.ext.sqlalchemy import SQLAlchemy()`
# we use an extended version which is more friendlier to tests
from .database import db as _db

db = _db


def create_app(config_name):
    """Create application using the given configuration.

    Application factory which takes as an argument the name of the
    configuration to use for the application. It then returns the
    created application.

    Args:
      config_name (string): name of the configuration.

    Returns:
      Flask: the created application.

    """
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    config[config_name].init_app(app)

    db.init_app(app)  # <--- as you've mentioned. =)
    ...
    return app

Although this whole SignallingSession problem has been fixed already with flask-sqlalchemy/pull/249 , it wont probably be released yet untill the whole 2.1 milestone has been completed.

I tried your first recommendation, but using pytest.set_trace(), I can see that the session fixture uses a different sqlachemy session instance as with the one factory boy uses (i checked using model_factory._meta.sqlalchemy_session).

I also tried your second recommendation, but I always encounter an error with using outside the application/request context. Probably a problem on where I placing the get_session function.

But nevertheless, somehow I was able to make factory boy use the same session instance with my session fixture by encapsulating each factory class inside a pytest fixture which injects my session fixture. see example below:

@pytest.fixture(scope='function')
def model_factory(session):
    Class ModelFactory(factory.alchemy.SQLAlchemyModelFactory):
        Class Meta:
            model = model
            sqlalchemy_session = session
        name = factory.Sequence(lambda n: "Model %d" % n)
   return ModelFactory


def test_session_instance(session, model_factory):
   assert session == model_factory._meta.sqlalchemy_session

Don’t know though if this is a good solution.

EDIT: I retried your first recommendation, I think problem lies on how or on when I import factories, If I import the factories inside the test function itself, it will have the same session instance.

# app/factories/__init__.py

import factory
from factory.alchemy import SQLAlchemyModelFactory

from app import models, db

class SomeModelFactory(SQLAlchemyModelFactory):
    class Meta:
        model = models.SomeModel
        sqlalchemy_session = db.session

    name = factory.Sequence(lambda n: "SomeModel %d" % n)
# tests/test_some_model.py

def test_session_instance(session):
   from app.factories import SomeModelFactory
   assert session == SomeModelFactory._meta.sqlalchemy_session  # this will pass

of course to avoid having to do imports inside the test function, I made a fixture which imports the whole factories modules itself and return it.

@pytest.fixture(scope='function')
def factories(session, request):
    import app.factories as _factories

    return _factories

EDIT: Looks like the above import strategy will only be true for the first time or test. I did some re-reading on a few blogs and stack posts, and I ended up with the second recommendation on this stack post, I probably didn’t pay attention to it that much before because I was using in-memory or sqlite database for the tests.

@pytest.fixture(scope='function')
def session(db, request):
    db.session.begin_nested()

    def teardown():
        db.session.rollback()
        db.session.close()

    request.addfinalizer(teardown)
    return db.session

then i just declare and import my factories as just with the documentation.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Object Relational Tutorial (1.x API)
The SQLAlchemy Object Relational Mapper presents a method of associating user-defined Python classes with database tables, and instances of those classes ( ...
Read more >
Flask Database Integration with SQLAlchemy - Section.io
In this article we will understand how to work with SQLAlchemy in a Flask web application. Storing data is an integral component of...
Read more >
SQLAlchemy Integration — spyne 2.11.0 documentation
In this tutorial, we talk about using Spyne tools that make it easy to deal with database-related operations using SQLAlchemy. SQLAlchemy is a...
Read more >
SQLAlchemy ORM Tutorial for Python Developers - Auth0
SQLAlchemy is a library that facilitates the communication between Python programs and databases. Most of the times, this library is used as an ......
Read more >
How to Use Flask-SQLAlchemy to Interact with Databases in a ...
For example, if you modify your model by adding a new column, and run the db.create_all() function, the change you make to the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found