Support pytest verbs test-system (was: Unable to rollback transactions using pytest and in_transaction context manager)
See original GitHub issueDescribe the bug
I wrote a pytest fixture that should be automatically run for each test. This fixture starts a new transaction using the in_transaction context manager, yields, and then tries to roll back. But the fixture breaks when it tries to roll back with an error that the token was created in a different context like this:
ValueError: <Token var=<ContextVar name='default' default=<tortoise.backends.asyncpg.client.AsyncpgDBClient object at 0x7f8c294a6670> at 0x7f8c294b2810> at 0x7f8c285a7140> was created in a different Context
To Reproduce
- Install 
pytest,pytest-asyncio,tortoise-ormandasyncpg. - Start the PostgreSQL server and create a new table with the contents:
 
CREATE TABLE mymodelfortest (
	id VARCHAR(255) PRIMARY KEY,
	name TEXT DEFAULT 'text'
);
- Create a new 
test_tortoise.pytest file: 
import contextlib
import pytest
from tortoise import Tortoise, fields, models
from tortoise.transactions import in_transaction
class MyModelForTest(models.Model):
    id = fields.CharField(pk=True, max_length=255)
    name = fields.TextField(default="text")
@pytest.fixture(autouse=True)
async def init_db():
    await Tortoise.init(
        db_url="postgres://postgres:postgres@localhost:5432/postgres",
        modules={"models": ["test_tortoise"]},
    )
    yield
    await Tortoise.close_connections()
@pytest.fixture(autouse=True)
async def db_transaction(init_db):
    with contextlib.suppress(RuntimeError):
        async with in_transaction():
            yield
            raise RuntimeError
@pytest.mark.asyncio
async def test_a():
    await MyModelForTest.create(id="test")
    assert (await MyModelForTest.all()) == [MyModelForTest(id="test")]
@pytest.mark.asyncio
async def test_b():
    assert (await MyModelForTest.all()) == []
- Run 
pytestand get a message about tests teardown errors and some failed tests: 
$ pytest test_tortoise.py
====================================================================================== test session starts ======================================================================================
platform linux -- Python 3.8.1, pytest-5.3.5, py-1.8.1, pluggy-0.13.1
rootdir: /home/nik/CodeSources/tortoise-test
plugins: asyncio-0.10.0
collected 2 items                                                                                                                                                                               
test_tortoise.py .EFE                                                                                                                                                                     [100%]
============================================================================================ ERRORS =============================================================================================
__________________________________________________________________________________ ERROR at teardown of test_a __________________________________________________________________________________
init_db = None
    @pytest.fixture(autouse=True)
    async def db_transaction(init_db):
        with contextlib.suppress(RuntimeError):
            async with in_transaction():
                yield
>               raise RuntimeError
E               RuntimeError
test_tortoise.py:28: RuntimeError
During handling of the above exception, another exception occurred:
init_db = None
    @pytest.fixture(autouse=True)
    async def db_transaction(init_db):
        with contextlib.suppress(RuntimeError):
            async with in_transaction():
                yield
>               raise RuntimeError
test_tortoise.py:28: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <tortoise.backends.base.client.TransactionContextPooled object at 0x7f8c28e240a0>, exc_type = <class 'RuntimeError'>, exc_val = RuntimeError()
exc_tb = <traceback object at 0x7f8c285bc1c0>
    async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
        if not self.connection._finalized:
            if exc_type:
                # Can't rollback a transaction that already failed.
                if exc_type is not TransactionManagementError:
                    await self.connection.rollback()
            else:
                await self.connection.commit()
>       current_transaction_map[self.connection_name].reset(self.token)
E       ValueError: <Token var=<ContextVar name='default' default=<tortoise.backends.asyncpg.client.AsyncpgDBClient object at 0x7f8c294a6670> at 0x7f8c294b2810> at 0x7f8c285a7140> was created in a different Context
../../.virtualenvs/tortoise-test-aUW8XuHC-py3.8/lib/python3.8/site-packages/tortoise/backends/base/client.py:173: ValueError
__________________________________________________________________________________ ERROR at teardown of test_b __________________________________________________________________________________
init_db = None
    @pytest.fixture(autouse=True)
    async def db_transaction(init_db):
        with contextlib.suppress(RuntimeError):
            async with in_transaction():
                yield
>               raise RuntimeError
E               RuntimeError
test_tortoise.py:28: RuntimeError
During handling of the above exception, another exception occurred:
init_db = None
    @pytest.fixture(autouse=True)
    async def db_transaction(init_db):
        with contextlib.suppress(RuntimeError):
            async with in_transaction():
                yield
>               raise RuntimeError
test_tortoise.py:28: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <tortoise.backends.base.client.TransactionContextPooled object at 0x7f8c284e7100>, exc_type = <class 'RuntimeError'>, exc_val = RuntimeError()
exc_tb = <traceback object at 0x7f8c29516c00>
    async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
        if not self.connection._finalized:
            if exc_type:
                # Can't rollback a transaction that already failed.
                if exc_type is not TransactionManagementError:
                    await self.connection.rollback()
            else:
                await self.connection.commit()
>       current_transaction_map[self.connection_name].reset(self.token)
E       ValueError: <Token var=<ContextVar name='default' default=<tortoise.backends.asyncpg.client.AsyncpgDBClient object at 0x7f8c28e155b0> at 0x7f8c285d8e00> at 0x7f8c284db680> was created in a different Context
../../.virtualenvs/tortoise-test-aUW8XuHC-py3.8/lib/python3.8/site-packages/tortoise/backends/base/client.py:173: ValueError
=========================================================================================== FAILURES ============================================================================================
____________________________________________________________________________________________ test_b _____________________________________________________________________________________________
    @pytest.mark.asyncio
    async def test_b():
>       assert (await MyModelForTest.all()) == []
E       assert [<MyModelForTest: test>] == []
E         Left contains one more item: <MyModelForTest: test>
E         Use -v to get the full diff
test_tortoise.py:39: AssertionError
============================================================================ 1 failed, 1 passed, 2 errors in 20.18s =============================================================================
Expected behavior Transactions should be able to roll back after the test is completed and the fixture tries to teardown, and all tests must pass.
Additional context
OS: Linux 5.5.2-arch1-1
PostgreSQL: 12.1-alpine for docker
Docker version: 19.03.5-ce
Python: 3.8.1
Dependencies:
aiosqlite==0.11.0
asyncpg==0.20.1
attrs==19.3.0
ciso8601==2.1.3
more-itertools==8.2.0
packaging==20.1
pluggy==0.13.1
py==1.8.1
pyparsing==2.4.6
PyPika==0.35.21
pytest==5.3.5
pytest-asyncio==0.10.0
six==1.14.0
tortoise-orm==0.15.9
typing-extensions==3.7.4.1
wcwidth==0.1.8
I previously used tortoise-orm 0.14.1 and solved this issue using this fixture:
@pytest.fixture(autouse=True)
async def db_transaction():
    transaction = await start_transaction()
    yield
    await transaction.rollback()
But since start_transaction is deprecated, so I tried changing it for the in_transaction context manager and ran into this problem.
Issue Analytics
- State:
 - Created 4 years ago
 - Reactions:5
 - Comments:7 (5 by maintainers)
 

Top Related StackOverflow Question
Ok, I get that. Right now I fixed it similar to how it was done in the helpers from
tortoise.contrib.test. I just handle database schema migrations(usingalembic) in a separate fixture without transactions. But a little later I would like to try to add support forpytestfixtures, if someone else does not.We depend on the
UnitTestinfrastructure to manage DB states during testing. The pytest verbs are a totally different system unfortunately.You can use pytest, and write tests like so: https://tortoise-orm.readthedocs.io/en/latest/contrib/unittest.html
Or you could consider adding support for pytest verbs as a PR?