1

I am using Pytest to test a Flask + SQLAlchemy application. This is the content of tests/contftest.py

import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from flask import _app_ctx_stack
from flask.ext.sqlalchemy import SQLAlchemy, BaseQuery
from package.myapp import create_app
from package.config import DefaultConfig

DbSession = scoped_session(
        sessionmaker(),
        scopefunc=_app_ctx_stack.__ident_func__
    )
@pytest.fixture(scope='session')
def app(request):
    _app = create_app()
    _app.debug = False

    _app.engine = create_engine(_app.config['SQLALCHEMY_DATABASE_URI'], connect_args={"options": "-c timezone=utc"})
    global DbSession
    DbSession.configure(bind=_app.engine, query_cls=BaseQuery)

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

    @_app.teardown_appcontext
    def teardown(exception=None):
        ctx.pop()
        global DbSession
        if DbSession:
            DbSession.remove()


    request.addfinalizer(teardown)
    return _app

When I run pytest, I get this error message

___________________ ERROR at teardown of test_create_project ___________________

exception = None

    @_app.teardown_appcontext
    def teardown(exception=None):
>       ctx.pop()

tests/conftest.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../.virtualenvs/quest-backend/lib/python3.6/site-packages/flask/ctx.py:189: in pop
    self.app.do_teardown_appcontext(exc)
../../.virtualenvs/quest-backend/lib/python3.6/site-packages/flask/app.py:1892: in do_teardown_appcontext
    func(exc)
tests/conftest.py:31: in teardown
    ctx.pop()
E   RecursionError: maximum recursion depth exceeded
!!! Recursion detected (same locals & position)

2 Answers 2

3

I don't think you are supposed to call ctx.pop() in your teardown function as the call to pop() invokes the registered teardown callbacks (hence the infinite recurAppContext calls Flask.do_teardown_appcontext() which contains the following:

for func in reversed(self.teardown_appcontext_funcs):
    func(exc)

You should call ctx.pop() when you are destroying your fixture.

Sign up to request clarification or add additional context in comments.

Comments

1

Expanding Roman Kutlak's answer, here is how I would rewrite your app fixture:

@pytest.fixture(scope='session')
def app():
    _app = create_app()
    _app.debug = False

    _app.engine = create_engine(_app.config['SQLALCHEMY_DATABASE_URI'], connect_args={"options": "-c timezone=utc"})
    # session should probably not be global?..
    DbSession = scoped_session(
        sessionmaker(),
        scopefunc=_app_ctx_stack.__ident_func__
    )
    DbSession.configure(bind=_app.engine, query_cls=BaseQuery)

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

    # this function is specifically for app's teardown, don't call it again for fixture teardown
    @_app.teardown_appcontext
    def teardown(exception=None):
        if DbSession:
            DbSession.remove()

    # here is where tests will be executed
    yield _app

    # now tear-down our fixture (as apposed to flask app's teardown)
    ctx.pop()

It is not needed to use global keyword unless you want to assign a value to global variable from within inner scope.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.