factoryboy pytest session management - python

I am using pytest as framework for testing my application and I want to use pytest factoryboy as well. Thusfar, my conftest.py looks pretty much like the example:
import factory
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from model import model
engine = create_engine('sqlite://')
session = scoped_session(sessionmaker(bind=engine))
# Create tables
model.Base.metadata.create_all(engine)
class ExampleFactory(factory.alchemy.SQLAlchemyModelFactory):
class Meta:
model = model.ExampleClass
sqlalchemy_session = session
label = factory.Sequence(lambda n: u'object_%d' % n)
I have multiple factories like this. The problem is that when I use factories in this manner, the session will not be torn down every unit test. I'm basically using one big session for the lot of unit tests that I have. Not very ideal. Using fixtures I could refresh a session every unit test. Is there a way to do this using factoryboy pytest?

Just tried a solution found here that do the job pretty well without being too complicated or dirty: wrapping each factory into a fixture which is provided with an other function-scoped session fixture.
This could look like this for you:
#pytest.fixture
def session():
session = <session creation>
yield session
session.rollback()
session.close()
#pytest.fixture
def exemple_factory(session):
class ExampleFactory(factory.alchemy.SQLAlchemyModelFactory):
class Meta:
model = model.ExampleClass
sqlalchemy_session = session
label = factory.Sequence(lambda n: u'object_%d' % n)
return ExampleFactory

If you are using pytest-flask-sqlalchemy plugin try to add the following fixture to your conftest.py
#pytest.fixture(autouse=True)
def set_session_for_factories(db_session):
ExampleFactory._meta.sqlalchemy_session = db_session()
and in your app config
SQLALCHEMY_ENGINE_OPTIONS = {
'poolclass': NullPool,
}

Related

Cannot patch sqlalchemy database engine

I am using SQLAlchemy (note: not Flask_SQLAlchemy) for a python 3 project, and I'm trying to write tests for the database by patching the engine with a test engine that points to a test database (as opposed to the production database). In the past, I successfully patched Session, and had working tests, but I recently switched to using the "insert" method, which is executed using engine.execute(), as opposed to a context managed session scope which was invoked using with session_scope() as session:
So heres the setup: I'm using a db_session module to establish a common session to be used by all DB functions:
import sys
import os
import logging
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from database.util import managed_session
import config
logger = logging.getLogger('default')
dirname = os.path.dirname
sys.path.append(dirname(dirname(__file__)))
engine = create_engine(config.POSTGRES_URI)
Session = sessionmaker(bind=engine)
def session_scope():
return managed_session(Session)
and then in the crud_function file we have a setup as follows:
import logging
import re
from collections import defaultdict
from sqlalchemy import desc
from sqlalchemy.exc import IntegrityError
from database.db_session import session_scope, engine, Session
from database.models import *
from database.util import windowed_query
from sqlalchemy.dialects.postgresql import insert
import pandas as pd
def store_twitter_user(unprotected_row):
'''
Creates a TwitterUser object from the given attributes, adds it to the session, and then commits it to the database.
:param attributes:
:return:
'''
row = defaultdict(lambda: None, unprotected_row)
pg_upsert(TwitterUser, row)
def pg_upsert(model, row):
'''Performs an UPDATE OR INSERT ON CONFLICT (Upsert), which is a special SQL command for Postgres dbs.
More info here: http://docs.sqlalchemy.org/en/latest/dialects/postgresql.html#insert-on-conflict-upsert
'''
try:
insert_stmt = insert(model.__table__).values(row)
do_update_stmt = insert_stmt.on_conflict_do_update(constraint=model.__table__.primary_key, set_=row)
engine.execute(do_update_stmt)
logger.debug('New {} stored successfully!'.format(type(object)))
return True
except IntegrityError as e:
if re.search('violates foreign key constraint .* Key \(author_id\)=\(\d+\) is not present in table', str(e.args)):
# Sends exception to celery task which will retry the task for a certain number of times
raise
except Exception as err:
logger.error('pg_upsert: An error occurred while trying to store the new {}: {}'.format(model.__mapper__, err))
return False
database.models just contains a bunch of classes used to create DB models for SQLAlchemy, like as follows:
class User(Base):
__tablename__ = 'users'
id = Column(BigInteger, primary_key=True)
name = Column(String())
screen_name = Column(String())
location = Column(String(), index=True)
friends_count = Column(Integer)
created_at = Column(DateTime)
time_zone = Column(String())
lang = Column(String())
Now here's the test file:
engine = create_engine(config.POSTGRES_TEST_URI)
class TestDBCRUD(unittest.TestCase):
ClassIsSetup = False
ReadyForTeardown = False
def setUp(self):
"""
Creates all the tables in the test DB
:return:
"""
if not self.ClassIsSetup:
print("SETTING UP!!!")
Base.metadata.create_all(engine)
self.__class__.ClassIsSetup = True
def tearDown(self):
"""
Deletes all test DB data and destroys the tables after a test is finished
:return:
"""
if self.ReadyForTeardown:
print("TEARING DOWN!!!")
Base.metadata.drop_all(engine)
self.__class__.ReadyForTeardown = False
#patch('database.crud.db_crud_functions.Session')
#patch('database.crud.db_crud_functions.engine', autospec=True)
#patch('database.db_session.Session')
#patch('database.db_session.engine', autospec=True)
def test_00_store_user(self, mock_session_engine, mock_session_Session, mock_engine, mock_session):
print("testing store user!")
Session = sessionmaker()
Session.configure(bind=engine)
mock_session_Session.return_value = Session()
mock_session_engine.return_value = engine
mock_session.return_value = Session()
mock_engine.return_value = engine
user = User(id=6789, screen_name="yeti")
user_dict = {'id': 6789, 'screen_name': "yeti"}
store_twitter_user(user_dict)
with managed_session(Session) as session:
retrieved_user = session.query(User).first()
print("users are: {}".format(retrieved_user))
self.assertEqual(user.id, retrieved_user.id)
self.assertEqual(user.screen_name, retrieved_user.screen_name)
You'll notice a stupid amount of patches on top of the test function, and that is to show that I've tried to patch the engine and session from multiple locations. I've read that patches should be made where objects are used, and not where they are imported from, so I tried to cover all the bases. It doesn't matter, the test function always ends up inserting a user into the production database, and not into the test database. Then, when the retrieval happens, it returns None.
File "tests/testdatabase/test_db_crud_functions.py", line 59, in test_00_store_user
self.assertEqual(user.id, retrieved_user.id)
AttributeError: 'NoneType' object has no attribute 'id'
Again, before pg_upsert was added, I used:
with session_scope() as session:
session.add(something_here)
And session was successfully mocked to point to POSTGRES_TEST_URI, and not POSTGRES_URI. I'm at a loss here, please let me know if anything sticks out. Thanks!

How can I test my flask application using unittest?

I'm trying to test my flask application using unittest. I want to refrain from flask-testing because I don't like to get ahead of myself.
I've really been struggling with this unittest thing now. It is confusing because there's the request context and the app context and I don't know which one I need to be in when I call db.create_all().
It seems like when I do add to the database, it adds my models to the database specified in my app module (init.py) file, but not the database specified in the setUp(self) method.
I have some methods that must populate the database before every test_ method.
How can I point my db to the right path?
def setUp(self):
#self.db_gd, app.config['DATABASE'] = tempfile.mkstemp()
app.config['TESTING'] = True
# app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + app.config['DATABASE']
basedir = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + \
os.path.join(basedir, 'test.db')
db = SQLAlchemy(app)
db.create_all()
#self.app = app.test_client()
#self.app.testing = True
self.create_roles()
self.create_users()
self.create_buildings()
#with app.app_context():
# db.create_all()
# self.create_roles()
# self.create_users()
# self.create_buildings()
def tearDown(self):
#with app.app_context():
#with app.request_context():
db.session.remove()
db.drop_all()
#os.close(self.db_gd)
#os.unlink(app.config['DATABASE'])
Here is one of the methods that populates my database:
def create_users(self):
#raise ValueError(User.query.all())
new_user = User('Some User Name','xxxxx#gmail.com','admin')
new_user.role_id = 1
new_user.status = 1
new_user.password = generate_password_hash(new_user.password)
db.session.add(new_user)
Places I've looked at:
http://kronosapiens.github.io/blog/2014/08/14/understanding-contexts-in-flask.html
http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xvi-debugging-testing-and-profiling
And the flask documentation:
http://flask.pocoo.org/docs/0.10/testing/
one issue that your hitting is the limitations of flask contexts, this is the primary reason i think long and hard before including a flask extension into my project, and flask-sqlalchemy is one of the biggest offenders. i say this because in most cases it is completely unnecessary to depend on the flask app context when dealing with your database. Sure it can be nice, especially since flask-sqlalchemy does a lot behind the scenes for you, mainly you dont have to manually manage your session, metadata or engine, but keeping that in mind those things can easily be done on your own, and for doing that you get the benefit of unrestricted access to your database, with no worry about the flask context. here is an example of how to setup your db manually, first i will show the flask-sqlalchemy way, then the manual plain sqlalchemy way:
the flask-sqlalchemy way:
import flask
from flask_sqlalchemy import SQLAlchemy
app = flask.Flask(__name__)
db = SQLAlchemy(app)
# define your models using db.Model as base class
# and define columns using classes inside of db
# ie: db.Column(db.String(255),nullable=False)
# then create database
db.create_all() # <-- gives error if not currently running flask app
the standard sqlalchemy way:
import flask
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
# first we need our database engine for the connection
engine = sa.create_engine(MY_DB_URL,echo=True)
# the line above is part of the benefit of using flask-sqlalchemy,
# it passes your database uri to this function using the config value
# SQLALCHEMY_DATABASE_URI, but that config value is one reason we are
# tied to the application context
# now we need our session to create querys with
Session = sa.orm.scoped_session(sa.orm.sessionmaker())
Session.configure(bind=engine)
session = Session()
# now we need a base class for our models to inherit from
Model = declarative_base()
# and we need to tie the engine to our base class
Model.metadata.bind = engine
# now define your models using Model as base class and
# anything that would have come from db, ie: db.Column
# will be in sa, ie: sa.Column
# then when your ready, to create your db just call
Model.metadata.create_all()
# no flask context management needed now
if you set your app up like that, any context issues your having should go away.
as a separate answer, to actually just force what you need to work, you can just use the test_request_context function, ie: in setup do: self.ctx = app.test_request_context() then just activate it, self.ctx.push() and when your done get rid of it, ie in tearDown: self.ctx.pop()

SQLAlchemy freezing application

I am using SQLAlchemy in my python command line app. The app is basically reading a set of URLs and doing inserts into a postgreql database based on the data.
After about the same number of inserts (give or take a few), the entire app freezes.
Having seen python sqlalchemy + postgresql program freezes I am assuming I am doing something wrong with the SQLAlchemy Session (although I am not using drop_all(), which seemed to be the cause of that issue). I've tried a couple of things but thus far they have had no impact.
Any hints or help would be welcome. If my integration of SQLAlchemy into my app is incorrect, a pointer to a good example of doing it right would also be welcome.
My code is as follows:
Set up the sql alchemy base:
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
Create the session info and attach it to the Base
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
engine = create_engine("sqlite:///myapp.db")
db_session = scoped_session(sessionmaker(bind=engine))
Base.query = db_session.query_property()
Base.scoped_db_session = db_session
Create my model from Base and make use of the session
class Person(Base):
def store(self):
if self.is_new():
self.scoped_db_session.add(self)
self.scoped_db_session.commit()
If I create enough objects of type Person and call store(), the app eventually freezes.
Managed to solve the problem. Turns out that my implementation is specifically on the don't do it this way list (see http://docs.sqlalchemy.org/en/latest/orm/session_basics.html#session-frequently-asked-questions E.g. don't do this) and I was not managing the session correctly
To solve my problem I moved the session out of the model into a separate class, so instead of having calls like:
mymodel.store()
I now have:
db.current_session.store(mymodel)
where db is an instance of my custom DBContext below:
from contextlib import contextmanager
from sqlalchemy.orm import scoped_session, sessionmaker
class DbContext(object):
def __init__(self, engine, session=None):
self._engine = engine
self._session = session or scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=self._engine))
self.query = self._session.query_property()
self.current_session = None
def start_session(self):
self.current_session = self._session()
def end_session(self):
if self.current_session:
self.current_session.commit()
self.current_session.close()
self.current_session = None
#contextmanager
def new_session(self):
try:
self.start_session()
yield
finally:
self.end_session()
When you want to store one or more model objects, call DBContext.start_session() to start a clean session. When you finally want to commit, call DBContext.end_session().

Custom sqlite database for unit tests for code using peewee ORM

I am trying to implement a many-to-many scenario using peewee python ORM and I'd like some unit tests. Peewee tutorial is great but it assumes that database is defined at module level then all models are using it. My situation is different: I don't have a source code file (a module from python's point of view) with tests which I run explicitly, I am using nose which collects tests from that file and runs them.
How do I use a custom database only for models instantiated in tests (which are being run by nose)? My goal is to use an in-memory database for tests only, to speedup the testing process.
I just pushed a commit today that makes this easier.
The fix is in the form of a context manager which allows you to override the database of a model:
from unittest import TestCase
from playhouse.test_utils import test_database
from peewee import *
from my_app.models import User, Tweet
test_db = SqliteDatabase(':memory:')
class TestUsersTweets(TestCase):
def create_test_data(self):
# ... create a bunch of users and tweets
for i in range(10):
User.create(username='user-%d' % i)
def test_timeline(self):
with test_database(test_db, (User, Tweet)):
# This data will be created in `test_db`
self.create_test_data()
# Perform assertions on test data inside ctx manager.
self.assertEqual(Tweet.timeline('user-0') [...])
# once we exit the context manager, we're back to using the normal database
See the documentation and have a look at the example testcases:
Context manager
Testcases showing how to use
To not include context manager in every test case, overwrite run method.
# imports and db declaration
class TestUsersTweets(TestCase):
def run(self, result=None):
with test_database(test_db, (User, Tweet)):
super(TestUsersTweets, self).run(result)
def test_timeline(self):
self.create_test_data()
self.assertEqual(Tweet.timeline('user-0') [...])
I took the great answers from #coleifer and #avalanchy and took them one step further.
In order to avoid overriding the run method on every TestCase subclass, you can use a base class... and I also like the idea of not having to write down every model class I work with, so I came up with this
import unittest
import inspect
import sys
import peewee
from abc import ABCMeta
from playhouse.test_utils import test_database
from business_logic.models import *
test_db = peewee.SqliteDatabase(':memory:')
class TestCaseWithPeewee(unittest.TestCase):
"""
This abstract class is used to "inject" the test database so that the tests don't use the real sqlite db
"""
__metaclass__ = ABCMeta
def run(self, result=None):
model_classes = [m[1] for m in inspect.getmembers(sys.modules['business_logic.models'], inspect.isclass) if
issubclass(m[1], peewee.Model) and m[1] != peewee.Model]
with test_database(test_db, model_classes):
super(TestCaseWithPeewee, self).run(result)
so, now I can just inherit from TestCaseWithPeewee and don't have to worry about anything else other than the test
Apparently, there's a new approach for the scenario described, where you can bind the models in the setUp() method of your test case:
Example from the official docs:
# tests.py
import unittest
from my_app.models import EventLog, Relationship, Tweet, User
MODELS = [User, Tweet, EventLog, Relationship]
# use an in-memory SQLite for tests.
test_db = SqliteDatabase(':memory:')
class BaseTestCase(unittest.TestCase):
def setUp(self):
# Bind model classes to test db. Since we have a complete list of
# all models, we do not need to recursively bind dependencies.
test_db.bind(MODELS, bind_refs=False, bind_backrefs=False)
test_db.connect()
test_db.create_tables(MODELS)
def tearDown(self):
# Not strictly necessary since SQLite in-memory databases only live
# for the duration of the connection, and in the next step we close
# the connection...but a good practice all the same.
test_db.drop_tables(MODELS)
# Close connection to db.
test_db.close()
# If we wanted, we could re-bind the models to their original
# database here. But for tests this is probably not necessary.
When using test_database I encountered problems with test_db not being initialized:
nose.proxy.Exception: Error, database not properly initialized before opening connection
-------------------- >> begin captured logging << --------------------
peewee: DEBUG: ('SELECT "t1"."id", "t1"."name", "t1"."count" FROM "counter" AS t1', [])
--------------------- >> end captured logging << ---------------------
I eventually fixed this by passing create_tables=True like so:
def test_timeline(self):
with test_database(test_db, (User, Tweet), create_tables=True):
# This data will be created in `test_db`
self.create_test_data()
According to the docs create_tables should default to True but it seems that isn't the case in the latest release of peewee.
For anyone who's using pytest, here's how I did it:
conftest.py
MODELS = [User, Tweet] # Also add get_through_model() for ManyToMany fields
test_db = SqliteDatabase(':memory:')
test_db.bind(MODELS, bind_refs=False, bind_backrefs=False)
test_db.connect()
test_db.create_tables(MODELS)
#pytest.fixture(autouse=True)
def in_mem_db(mocker):
mocked_db = mocker.patch("database.db", autospec=True) # "database.db" is where your app's code imports db from
mocked_db.return_value = test_db
return mocked_db
And voila, all your tests run with an in-memory sqlite database.

How to properly test a Python Flask system based on SQLAlchemy Declarative

I have a project that I've been working on for a while, which is written in Flask, and uses SQLAlchemy with the Declarative extension http://flask.pocoo.org/docs/patterns/sqlalchemy/. I've recently decided to start unit testing my project, but for the life of me, I can't seem to figure out how to make it work.
I looked at http://flask.pocoo.org/docs/testing/, but I can't seem to make it work.
I tried a mix of things from different websites, but am unable to find something that works correctly.
class StopsTestCase(unittest.TestCase):
def setUp(self):
self.engine = create_engine('sqlite:///:memory:')
self.session = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
bind=self.engine))
models.Base = declarative_base()
models.Base.query = self.session.query_property()
models.Base.metadata.create_all(bind=self.engine)
def test_empty_db(self):
stops = session.query(models.Stop).all()
assert len(stops) == 0
def tearDown(self):
session.remove()
if __name__ == '__main__':
unittest.main()
Unfortunately, the best I can seem to get, causes the following error.
OperationalError: (OperationalError) no such table: stops u'SELECT stops.agency_id AS stops_agency_id, stops.id AS stops_id, stops.name AS stops_name, stops."desc" AS stops_desc, stops.lat AS stops_lat, stops.lon AS stops_lon, stops.zone_id AS stops_zone_id \nFROM stops' ()
----------------------------------------------------------------------
Ran 1 test in 0.025s
FAILED (errors=1)
Any help on this would be greatly appreciated. If someone has ever been through this before, and made it work, I would like some pointers! Thanks in advance.
Based on what I found and how I got it working, here is a template solution that works for testing the underlying SQLAlchemy systems using the Declarative extension.**
import unittest
from database import Base
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
import models
class StopsTestCase(unittest.TestCase):
def setUp(self):
self.engine = create_engine('sqlite:///:memory:')
self.session = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
bind=self.engine))
Base.query = self.session.query_property()
Base.metadata.create_all(bind=self.engine)
#Create objects here
#These will likely involve something like the following for one of my stops
# stop1 = models.Stop(id=1, name="Stop 1")
# self.session.add(stop1)
# self.session.commit()
# But adding a stop to the database here will break the test below. Just saying.
def test_empty_db(self):
stops = self.session.query(models.Stop).all()
assert len(stops) == 0
def tearDown(self):
self.session.remove()
You're instantiating declarative_base again, you should be using the same instance you used as a base class for your models. Also, you seem to be using two different session instances, self.session and some module-global session. Try cleaning that up, too.

Categories

Resources