Small wrapper around asyncpg for specific experience and transactional testing.
from asyncpg_engine import Engine
engine = await Engine.create("postgres://guest:guest@localhost:5432/guest?sslmode=disable")
async with engine.acquire() as con:
# https://magicstack.github.io/asyncpg/current/api/index.html#asyncpg.connection.Connection
assert await con.fetchval("SELECT 1") == 1
You can specify custom encoder/decoder by subclassing Engine
:
from asyncpg_engine import Engine
import orjson
class MyEngine(Engine):
@staticmethod
async def _set_codecs(con: Connection) -> None:
# https://magicstack.github.io/asyncpg/current/api/index.html#asyncpg.connection.Connection.set_type_codec
await con.set_type_codec(
"json", encoder=orjson.dumps, decoder=orjson.loads, schema="pg_catalog"
)
Library includes pytest plugin with support for transactional testing.
To start using it install pytest
, enable plugin in your root conftest.py
and define postgres_url
fixture that returns database connection string:
pytest_plugins = ["asyncpg_engine"]
@pytest.fixture()
def postgres_url() -> str:
return "postgres://guest:guest@localhost:5432/guest?sslmode=disable"
Now you can use two fixtures:
db
that returnsEngine
instance:
async def test_returns_true(db):
async with db.acquire() as con:
assert await con.fetchval("SELECT true")
con
that returns already acquired connection:
async def test_returns_true(con):
assert await con.fetchval("SELECT true")
By default Engine
is configured for transactional testing, so every call to db.acquire
or con
usage will return the same connection with already started transaction. Transaction is rolled back at the end of test, so all your changes in db are rolled back too.
You can override this behaviour with asyncpg_engine
mark:
@pytest.mark.asyncpg_engine(transactional=False)
async def test_returns_true(con):
assert await con.fetchval("SELECT true")
@pytest.mark.asyncpg_engine(transactional=False)
async def test_returns_true_too(db):
async with db.acquire() as con:
assert await con.fetchval("SELECT true")
If you want to use your own custom Engine
subclass in tests you can define asyncpg_engine_cls
fixture that returns it:
from asyncpg_engine import Engine
class MyPrettyEngine(Engine):
pass
@pytest.fixture()
def asyncpg_engine_cls() -> typing.Type[MyPrettyEngine]:
return MyPrettyEngine
async def test_returns_my_pretty_engine(db: MyPrettyEngine) -> None:
assert isinstance(db, MyPrettyEngine)
First of all you should install Poetry.
- install project dependencies
make install
- run linters
make lint
- run tests
make test
- feel free to contribute!