Skip to main content

Ephemeral PostgreSQL instances for unit testing

Project description

isoladb

Ephemeral PostgreSQL instances for unit testing. No pre-installed PostgreSQL required — just Python 3.8+.

isoladb downloads pre-built PostgreSQL binaries (or uses your system installation), starts an isolated server, creates per-test databases, and cleans up automatically.

Installation

pip install isoladb

With pytest support:

pip install isoladb[pytest]

With psycopg (PostgreSQL client):

pip install isoladb[psycopg]

Quick Start

import psycopg
from isoladb import IsolaDB

with IsolaDB() as db:
    with psycopg.connect(db.url) as conn:
        conn.execute("CREATE TABLE users (id serial PRIMARY KEY, name text)")
        conn.execute("INSERT INTO users (name) VALUES ('Alice')")
        conn.commit()
        result = conn.execute("SELECT name FROM users").fetchone()
        assert result[0] == "Alice"
# Server and database are cleaned up automatically

Each IsolaDB() context manager creates a fresh, isolated database. The underlying PostgreSQL server is shared and reused across invocations with the same configuration.

Connection Properties

The context manager yields an object with these properties:

Property Description Example
db.url Full connection URL postgresql://postgres@localhost/isoladb_test_a1b2c3?host=/tmp/pg_xyz&port=54321
db.host Unix socket directory /tmp/pg_xyz
db.port Server port 54321
db.dbname Database name isoladb_test_a1b2c3
db.user Superuser name postgres

Works with any PostgreSQL client library:

# psycopg v3
conn = psycopg.connect(db.url)

# psycopg2
conn = psycopg2.connect(host=db.host, port=db.port, dbname=db.dbname, user=db.user)

# asyncpg
conn = await asyncpg.connect(host=db.host, port=db.port, database=db.dbname, user=db.user)

# SQLAlchemy
engine = create_engine(db.url)

PostgreSQL Binary Resolution

By default, isoladb looks for PostgreSQL in this order:

  1. System PostgreSQL — detected via pg_ctl on PATH (e.g., Homebrew, apt)
  2. Cached download — previously downloaded binaries in ~/.cache/isoladb
  3. Fresh download — fetched from Maven Central (~50MB, cached for future use)

To always use downloaded binaries instead of a system installation:

with IsolaDB(use_system_pg=False) as db:
    ...

Schema and Setup

Apply a SQL schema file automatically after each database is created:

with IsolaDB(schema="schema.sql") as db:
    # Tables from schema.sql are already created
    with psycopg.connect(db.url) as conn:
        conn.execute("INSERT INTO users (name) VALUES ('Alice')")

Or point to a directory of .sql files — they are sorted by filename and applied in order:

migrations/
  001_create_users.sql
  002_create_posts.sql
  003_seed_data.sql
with IsolaDB(schema="migrations/") as db:
    # All .sql files applied in sorted order
    ...

Non-.sql files in the directory are ignored.

For programmatic initialization (e.g., Alembic migrations), use a setup callable:

def apply_migrations(url):
    from alembic.config import Config
    from alembic import command
    cfg = Config("alembic.ini")
    cfg.set_main_option("sqlalchemy.url", url)
    command.upgrade(cfg, "head")

with IsolaDB(setup=apply_migrations) as db:
    ...

Both can be combined — schema is applied first, then setup.

RAM Disk

Run the PostgreSQL data directory on a RAM disk for faster I/O:

with IsolaDB(ram=True) as db:
    ...

Uses tmpfs on Linux and hdiutil RAM disk on macOS. Falls back to a regular temp directory if RAM disk creation fails.

Async Support

from isoladb import AsyncIsolaDB

async with AsyncIsolaDB() as db:
    conn = await asyncpg.connect(
        host=db.host, port=db.port, database=db.dbname, user=db.user
    )
    await conn.execute("CREATE TABLE test (id serial PRIMARY KEY)")
    await conn.close()

The async setup callable can be either sync or async:

async def apply_migrations(url: str) -> None:
    engine = create_async_engine(url)
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    await engine.dispose()

async with AsyncIsolaDB(setup=apply_migrations) as db:
    ...

Configuration

All options can be passed to IsolaDB() / AsyncIsolaDB():

Option Default Description
pg_version "17.2.0" PostgreSQL version (for downloaded binaries)
ram False Use RAM disk for the data directory
ram_size_mb 256 RAM disk size in megabytes
use_system_pg True Prefer system PostgreSQL over downloading
schema None Path to a SQL file or directory of .sql files
setup None Callable receiving the connection URL for custom setup
cache_dir ~/.cache/isoladb Directory for cached PostgreSQL binaries
startup_timeout 30.0 Seconds to wait for the server to start
pg_conf {} Extra postgresql.conf settings as {"key": "value"}

Pytest Plugin

isoladb includes a pytest plugin that provides fixtures automatically when isoladb[pytest] is installed.

Fixtures

isoladb — per-test fixture yielding an IsolaDBConnection with .url, .host, .port, .dbname:

def test_users(isoladb):
    with psycopg.connect(isoladb.url) as conn:
        conn.execute("CREATE TABLE users (id serial PRIMARY KEY, name text)")
        conn.execute("INSERT INTO users (name) VALUES ('Alice')")
        conn.commit()
        result = conn.execute("SELECT count(*) FROM users").fetchone()
        assert result[0] == 1

isoladb_engine — per-test fixture yielding a SQLAlchemy engine (requires sqlalchemy):

def test_with_engine(isoladb_engine):
    with isoladb_engine.connect() as conn:
        conn.execute(text("SELECT 1"))

isoladb_async — per-test async fixture (requires pytest-asyncio):

async def test_async(isoladb_async):
    conn = await asyncpg.connect(
        host=isoladb_async.host, port=isoladb_async.port,
        database=isoladb_async.dbname, user="postgres",
    )
    await conn.execute("SELECT 1")
    await conn.close()

isoladb_async_engine — per-test async SQLAlchemy engine (requires sqlalchemy[asyncio], asyncpg):

async def test_async_engine(isoladb_async_engine):
    async with isoladb_async_engine.connect() as conn:
        await conn.execute(text("SELECT 1"))

isoladb_server — session-scoped fixture exposing the underlying IsolaDBServer. Useful for custom fixture composition.

isoladb_setup — session-scoped fixture to override with a custom setup callable:

# conftest.py
@pytest.fixture(scope="session")
def isoladb_setup():
    def apply_migrations(url):
        from alembic.config import Config
        from alembic import command
        cfg = Config("alembic.ini")
        cfg.set_main_option("sqlalchemy.url", url)
        command.upgrade(cfg, "head")
    return apply_migrations

Ini Options

Configure in pyproject.toml, pytest.ini, or setup.cfg:

# pyproject.toml
[tool.pytest.ini_options]
isoladb_pg_version = "16.1.0"
isoladb_ram = true
isoladb_use_system_pg = false
isoladb_schema = "tests/schema.sql"
Option Default Description
isoladb_pg_version latest stable PostgreSQL version
isoladb_ram false Use RAM disk
isoladb_use_system_pg true Prefer system PostgreSQL
isoladb_schema none SQL schema file path

The pytest header shows which PostgreSQL binary is being used:

============================= test session starts ==============================
platform darwin -- Python 3.13.6
isoladb: PostgreSQL at /opt/homebrew/Cellar/postgresql@14/14.19

Requirements

  • Python 3.8+
  • No pre-installed PostgreSQL needed (downloads automatically if not found)
  • Linux (x86_64, arm64) or macOS (x86_64, arm64)

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

isoladb-0.1.0.tar.gz (19.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

isoladb-0.1.0-py3-none-any.whl (23.3 kB view details)

Uploaded Python 3

File details

Details for the file isoladb-0.1.0.tar.gz.

File metadata

  • Download URL: isoladb-0.1.0.tar.gz
  • Upload date:
  • Size: 19.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for isoladb-0.1.0.tar.gz
Algorithm Hash digest
SHA256 64613e5045b0c536fe0b5e26d3e4bd7727ca199af33e259915cb4f21436a9972
MD5 3fb32445785b0bb937e1dff057cbe853
BLAKE2b-256 0cb05cc1563dbc317f67d102b1227f3254fc3b966d3175e60fbe78387d8e9d4b

See more details on using hashes here.

Provenance

The following attestation bundles were made for isoladb-0.1.0.tar.gz:

Publisher: publish.yml on ystepanoff/isoladb

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file isoladb-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: isoladb-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 23.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for isoladb-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 035cee748a2fefb343ec1b1a1afdf4faf24a82d885846ddfa4313ca42ceea878
MD5 2bee62c4d767beed980014ee8e1eb896
BLAKE2b-256 6dcf0d4e22ea48085c76ef602056051009a8669777e86a93e06acbdd49691d71

See more details on using hashes here.

Provenance

The following attestation bundles were made for isoladb-0.1.0-py3-none-any.whl:

Publisher: publish.yml on ystepanoff/isoladb

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page