How to test python database interacting functions - python

I have some function in my python file course.py which is in proj/com/api/course
folder. These function interact with database.
I am using flask, sqlalchemy .
course.py
def create(**kwargs):
user = User()
session = Session()
user.name = kwargs['name']
user.email = kwargs['email']
session.add(user)
session.commit()
session.close()
return user
def get_data_by_id(user_id):
session = Session()
user = session.query(User).filter(User.id==user_id).first()
session.close()
return user
My question is where should I create testing module and how set connections with data base to test functions?

Related

How do I use scoped_session properly with multiple databases?

I am currently working on a flask app in which you can have multiple databases connected to it.
Each request to the app should be handled by a certain database depending on the url.
I am now trying to replace flask-sqlalchemy with sqlalchemy in order to use scoped-session to take care of my problem.
I have a session_registry in order to store the sessions:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
class SessionRegistry(object):
_registry = {}
def get_database_connection(self, name, **kwargs):
return self._registry[name]
def add_database_connection(self, url, name, **kwargs):
if url not in self._registry:
engine = create_engine(url)
Session = sessionmaker(bind=engine)
session = scoped_session(Session)
self._registry[name] = session
return True if self._registry[name] is not None else Fals
The problem that I have now is, that I don't know how to pass it to my routes in order to use that session. Here is an example class where I am trying to use it:
class SomeJob():
def get(self, lim=1000, order="asc"):
if order == "desc":
result = session.query(SomeModel).order_by(
SomeModel.id.desc()).limit(lim).all()
else:
result = session.query(SomeModel).order_by(
SomeModel.id.asc()).limit(lim).all()
# deserialize to json
schemaInstance = SomeSchema(many=True)
json_res = schemaInstance.dump(result)
# return json
return json_res
My question now is, how do I pass that session to the object properly?

sqlalchemy.exc.ResourceClosedError: This Connection is closed error

I cover the project with tests using pytest.
In each application (module), I created the tests folder, inside which placed the files with the application tests.
In each tests folder there are conftest fixtures for each application.
When I run the tests separately for each application (like pytest apps/users) everything works fine.
But when I run the tests entirely for the whole project (just pytest) for the first application the tests pass, but then it throws the sqlalchemy.exc.ResourceClosedError: This Connection is closed error for other application
Example of conftest.py
import os
import pytest
TESTDB = "test.db"
TESTDB_PATH = os.path.join(basedir, TESTDB)
#pytest.fixture(scope="session")
def app(request):
"""Session-wide test `Flask` application."""
app = create_app("config.TestConfig")
# 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."""
if os.path.exists(TESTDB_PATH):
os.unlink(TESTDB_PATH)
def teardown():
_db.drop_all()
try:
os.unlink(TESTDB_PATH)
except FileNotFoundError:
pass
_db.app = app
_db.create_all()
permission = PermissionModel(title="can_search_articles")
role = RoleModel(title="API User", permissions=[permission])
tag = TagModel(name="Test tag")
article = ArticleModel(
title="Test article",
legal_language="en",
abstract="",
state="Alaska",
tags=[tag],
)
_db.session.add_all([role, permission, tag, article])
_db.session.commit()
user1 = UserModel(email="test#gmail.com", role_id=role.id)
user2 = UserModel(email="test2#gmail.com")
_db.session.add_all([user1, user2])
# Commit the changes for the users
_db.session.commit()
request.addfinalizer(teardown)
return _db
#pytest.fixture(scope="function")
def session(db, request):
"""Creates a new database session for a test."""
connection = db.engine.connect()
transaction = connection.begin()
options = dict(bind=connection, binds={})
session = db.create_scoped_session(options=options)
db.session = session
def teardown():
transaction.rollback()
connection.close()
session.remove()
request.addfinalizer(teardown)
return session
#pytest.fixture(scope="module")
def client(app):
client = app.test_client()
ctx = app.app_context()
ctx.push()
yield client
ctx.pop()
structure of project
proj/
__apps/
____articles/
______models.py, views.py, __init__.py etc
______tests/
________|__init__.py
________test_models.py
________conftest.py
____users/
______models.py, views.py, __init__.py etc
______tests/
________|__init__.py
________test_models.py
________conftest.py
______init__.py # Here I load my models, register blueprints
__main.py # Here I run my application
You can not have two simultaneous connections to sqlite database. Also you have two connections here, one explicit in session fixture, you open and close it by your self, and second implicit in db fixture (_db.session), probably closing not happen here. So, try use connection implicit and only once, instead db and session fixtures make only session fixture:
#pytest.fixture
def session(app):
"""Creates a new database session for a test."""
db.app = app
db.create_all()
with db.engine.connect() as connection:
with connection.begin() as transaction:
options = dict(bind=connection, binds={})
session = db.create_scoped_session(options=options)
db.session = session
prepare_data(session)
yield session
transaction.rollback()
db.drop_all()
here prepare_data is your data filling of new db:
def prepare_data(session):
permission = PermissionModel(title="can_search_articles")
role = RoleModel(title="API User", permissions=[permission])
tag = TagModel(name="Test tag")
article = ArticleModel(
title="Test article",
legal_language="en",
abstract="",
state="Alaska",
tags=[tag],
)
session.add_all([role, permission, tag, article])
session.commit()
user1 = UserModel(email="test#gmail.com", role_id=role.id)
user2 = UserModel(email="test2#gmail.com")
session.add_all([user1, user2])
# Commit the changes for the users
session.commit()
because session fixture here is function-scope, in each test you will have your one database. It will be more practical dont fill database fully each time, but split this prepare_data to few separate fixtures, each for one object, and use them in tests where they exactly needed.

Flask pytest testing database

I am trying to write some test for my application. I can manage to test URLs which does not contain anything related to db.
#pytest.fixture
def client():
"""
Load pytest fixture.
:return: test_client()
"""
# yield application.test_client()
db_fd, application.config['DATABASE'] = tempfile.mkstemp()
db.create_all()
application.config['TESTING'] = True
client = application.test_client()
yield client
os.unlink(application.config['DATABASE'])
my test file:
def test_membership_register(client):
"""Test entry point of website."""
rv = client.get('/register')
new_user = User(
username='test_user_1',
email='test_email#test1.com',
password='test1_Password',
)
db.session.add(new_user)
db.session.commit()
assert 200 == rv.status_code
ERROR:
E sqlalchemy.exc.InvalidRequestError: Table 'membership_user' is already defined for this MetaData instance. Specify 'extend_existing=True' to redefine options and columns on an existing Table object.

SQLite: the database is locked

I'm using flask with sqlalchemy and sqlite db. I have 2 ajax that send some data from html to my .py file.
The problem is every time when i do any of these 2 operations, the second one become unavailable because of lock of db. Also, if first chosen action will be deleting, then exception firing no matter what operation will be chosen after. with first choice of adding, we can add without limitations that's strange too, because functions seem similar.
I've tried timeouts, closing sessions in a different ways, the result is always the same.
Here are two functions-handlers:
app = Flask(__name__)
csrf = CSRFProtect(app)
app.config.from_object('config')
db = SQLAlchemy(app)
import forms
import models
#app.route('/delete', methods = ['GET', 'POST'])
def delete():
if request.method == "POST":
if request.form['type'] == "delete":
print("delete")
engine = create_engine(SQLALCHEMY_DATABASE_URI)
Session = sessionmaker(bind=engine)
session = Session()
try:
print("try")
requested = request.form['id']
print(requested)
models.Income.query.filter(models.Income.id == requested).delete()
session.commit()
except:
print("rollback")
session.rollback()
finally:
print("fin")
session.close()
ellist = models.Income.query.all()
return render_template("incomeSection.html", list=ellist)
#app.route('/add', methods=['GET', 'POST'])
def add():
if request.method == "POST":
if request.form['type'] == "add":
print('add')
engine = create_engine(SQLALCHEMY_DATABASE_URI)
Session = sessionmaker(bind=engine)
session = Session()
try:
print("try")
newItem = models.Income(name=request.form['name'], tag=request.form['tag'],
account=request.form['account'],
date=date(*(int(i) for i in request.form['date'].split("-"))))
session.add(newItem)
session.commit()
except:
print('rollback')
session.rollback()
finally:
print("fin")
session.close()
ellist = models.Income.query.all()
print(ellist)
return render_template("incomeSection.html", list=ellist)
I've read that this exception caused by non-closed connections, but I have .close() in every finally block. I think the problem might be because of the db = SQLAlchemy(app) but I don't know how to fix if that is the case. Because I use this variable to connect with db in forms.py where I have the form template and in models.py where I defined my tables within db.
So, aperrently, thete was an issue with number of connections.
The thing that solved my problem was the context manager for sqlalchemy, i used this one:
class SQLAlchemyDBConnection(object):
def __init__(self, connection_string):
self.connection_string = connection_string
self.session = None
def __enter__(self):
engine = create_engine(self.connection_string)
Session = sessionmaker()
self.session = Session(bind=engine)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.session.commit()
self.session.close()
and in the handler just
with SQLAlchemyDBConnection(SQLALCHEMY_DATABASE_URI) as db:
newItem = models.Income(*your params*)
db.session.add(newItem)
And now it works fine, but i still don't know what was the issue in version that was earlier. They are seem to be same just with or without context manager

Is having multiple SQLAlchemy sessions in the same controller okay, or should I put them all into one session?

So I have a controller that renders a page. In the controller, I call multiple functions from the model that create its own sessions. For example:
def page(request):
userid = authenticated_userid(request)
user = User.get_by_id(userid)
things = User.get_things()
return {'user': user, 'things': things}
Where in the model I have:
class User:
...
def get_by_id(self, userid):
return DBSession.query(User)...
def get_things(self):
return DBSession.query(Thing)...
My question is, is creating a new session for each function optimal, or should I start a session in the controller and use the same session throughout the controller (assuming I'm both querying as well as inserting into the database in the controller)? Ex.
def page(request):
session = DBSession()
userid = authenticated_userid(request)
user = User.get_by_id(userid, session)
things = User.get_things(session)
...
return {'user': user, 'things': things}
class User:
...
def get_by_id(self, userid, session=None):
if not session:
session = DBSession()
return session.query(User)...
def get_things(self, session=None):
if not session:
session = DBSession()
return session.query(Thing)...
Your first code is OK, if your DBSession is a ScopedSession. DBSession() is not a constructor then, but just an accessor function to thread-local storage. You might speed up things a bit by passing the session explicitly, but premature optimization is the root of all evil.

Categories

Resources