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:
- System PostgreSQL — detected via
pg_ctlonPATH(e.g., Homebrew, apt) - Cached download — previously downloaded binaries in
~/.cache/isoladb - 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file isoladb-0.1.1.tar.gz.
File metadata
- Download URL: isoladb-0.1.1.tar.gz
- Upload date:
- Size: 20.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7ea5aa2dbd1e371a6919e8f75867e35f46041824a20f6fb85231259225735b1e
|
|
| MD5 |
f8cf1c8dea716198df617c3512ed1b14
|
|
| BLAKE2b-256 |
fd91509e8fb4414f0541f351de710e6603613204e9e63302c18be7ad9c9e6d3b
|
Provenance
The following attestation bundles were made for isoladb-0.1.1.tar.gz:
Publisher:
publish.yml on ystepanoff/isoladb
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
isoladb-0.1.1.tar.gz -
Subject digest:
7ea5aa2dbd1e371a6919e8f75867e35f46041824a20f6fb85231259225735b1e - Sigstore transparency entry: 1060265650
- Sigstore integration time:
-
Permalink:
ystepanoff/isoladb@c2b69f5ddd43efa125093e36f00f7e64e82df307 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/ystepanoff
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c2b69f5ddd43efa125093e36f00f7e64e82df307 -
Trigger Event:
release
-
Statement type:
File details
Details for the file isoladb-0.1.1-py3-none-any.whl.
File metadata
- Download URL: isoladb-0.1.1-py3-none-any.whl
- Upload date:
- Size: 24.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1be219d0b1984446a89420c98b116da5ddbb5f64ec60a556909277a9409c3345
|
|
| MD5 |
1c2732e944eb4ba1cd35e57437bf8846
|
|
| BLAKE2b-256 |
0f027b961eeb848674921cc677afd6b0c6be27c1a159d23a4e510c27c30e8d54
|
Provenance
The following attestation bundles were made for isoladb-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on ystepanoff/isoladb
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
isoladb-0.1.1-py3-none-any.whl -
Subject digest:
1be219d0b1984446a89420c98b116da5ddbb5f64ec60a556909277a9409c3345 - Sigstore transparency entry: 1060265696
- Sigstore integration time:
-
Permalink:
ystepanoff/isoladb@c2b69f5ddd43efa125093e36f00f7e64e82df307 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/ystepanoff
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c2b69f5ddd43efa125093e36f00f7e64e82df307 -
Trigger Event:
release
-
Statement type: