Skip to content

Little wrapper around asyncpg for specific experience.

License

Notifications You must be signed in to change notification settings

jelmer/asyncpg-engine

 
 

Repository files navigation

asyncpg-engine

Small wrapper around asyncpg for specific experience and transactional testing.

Build Status Code style: black Python versions PyPi

Basic usage

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

Custom type conversions

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"
        )

Pytest plugin

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 returns Engine 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)

Development and contribution

First of all you should install Poetry using official instructions or solutions provided by your distro. Then install dependencies:

poetry install

Run PostgreSQL using provided docker-compose configuration:

docker-compose up  # run it in another terminal or add `-d` to daemonize

Project uses a combination of flake8, black, isort and mypy for linting and pytest for testing.

poetry run flake8
poetry run mypy ./
poetry run pytest

About

Little wrapper around asyncpg for specific experience.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Python 91.1%
  • Nix 8.9%