Generate realistic, referentially-consistent test data for PostgreSQL databases from their own schema.
Project description
sowdb
Generate realistic, referentially-consistent test data for PostgreSQL — straight from the schema that's already there.
Seeding a database for local development, demos, or load testing usually means
either writing brittle fixture scripts by hand or letting a generic faker
library spray random values that ignore your foreign keys, UNIQUE
constraints, and column lengths. sowdb reads your PostgreSQL schema,
figures out a safe insertion order from the foreign-key graph, and generates
values that actually respect NOT NULL, UNIQUE, VARCHAR lengths, and
foreign keys — so every row it produces is one Postgres will actually accept.
Install
pip install sowdb
Requires Python 3.11+ and a PostgreSQL database to introspect.
Quickstart
# Point sowdb at a database (or set PGHOST/PGUSER/PGPASSWORD/PGDATABASE instead of --dsn)
export SOWDB_DSN="postgresql+psycopg://user:password@localhost:5432/mydb"
# See the tables sowdb found and the order it would insert them in
sowdb inspect --dsn "$SOWDB_DSN"
# Generate 100 rows per table, reproducibly, without touching the database
sowdb generate --dsn "$SOWDB_DSN" --rows 100 --seed 42 --dry-run
# Actually insert it
sowdb generate --dsn "$SOWDB_DSN" --rows 100 --seed 42
# Or write portable SQL / CSV instead of inserting directly
sowdb generate --dsn "$SOWDB_DSN" --rows 100 --output sql --out-dir ./out
sowdb generate --dsn "$SOWDB_DSN" --rows 100 --output csv --out-dir ./out
Try it against the bundled example schemas:
createdb sowdb_demo
psql sowdb_demo -f examples/blog.sql
sowdb generate --dsn "postgresql+psycopg://localhost/sowdb_demo" --rows 30 --seed 1
How it works
- Introspect (
sowdb.introspect) — reflects tables, columns, types, nullability,UNIQUEconstraints, primary keys and foreign keys via SQLAlchemy. - Order (
sowdb.graph) — builds a dependency graph from foreign keys and computes a topological insertion order, so a table is only populated after every table it references. Cycles between tables raise a clear, actionable error instead of silently producing broken data. - Generate (
sowdb.generate+sowdb.providers) — for each column, picks a value provider: first by column name (email,created_at,price, ...), falling back to the column's SQL type. Foreign keys are always resolved to a value already generated for the referenced table. - Write (
sowdb.writers) — inserts in batches directly into the database, or exports to a portable.sqlfile or per-table CSV files.
CLI reference
sowdb inspect --dsn DSN [--schema SCHEMA] [--include-table TABLE ...]
sowdb generate --dsn DSN
[--rows N] # default 10
[--rows-for table=N ...] # per-table override, repeatable
[--seed N] # reproducible generation
[--batch-size N] # default 500
[--output db|sql|csv] # default db
[--out-dir PATH] # required for sql/csv
[--dry-run] # generate + validate, write nothing
[--schema SCHEMA]
[--include-table TABLE ...]
[--locale LOCALE] # Faker locale, default en_US
[--verbose]
sowdb can also be used as a library — cli.py only parses arguments and
delegates to sowdb.introspect, sowdb.graph, sowdb.generate and
sowdb.writers, all of which are plain, dependency-free-of-Typer Python.
Adding a custom value provider
Providers are matched in priority order: higher priority runs first. Register a new one without touching any core module:
from sowdb.providers import ColumnProvider, GenerationContext, register_provider
from sowdb.introspect import ColumnInfo
@register_provider(priority=100)
class IsbnProvider(ColumnProvider):
def matches(self, column: ColumnInfo) -> bool:
return column.name.lower() == "isbn"
def generate(self, column: ColumnInfo, context: GenerationContext) -> object:
return context.faker.isbn13()
Known limitations (v0.1)
- PostgreSQL only. Other engines are out of scope for this release.
GENERATED ALWAYS AS IDENTITYcolumns aren't supported. Postgres rejects explicit inserts into them withoutOVERRIDING SYSTEM VALUE. UseGENERATED BY DEFAULT AS IDENTITY(or classicSERIAL) instead, or exclude the table with--include-table.- Multi-table cycles of
NOT NULLforeign keys can't be ordered.sowdbdetects them and tells you which tables are involved; make one of the foreign keys nullable or exclude a table to break the cycle. - Composite primary keys are supported (
sowdbretries until the tuple is unique), but compositeUNIQUEconstraints spanning more than one column are not currently enforced during generation. - No statistical distributions. Values are plausible per-column, not
correlated across columns (e.g.
cityandcountrywon't necessarily match). - The whole generated dataset's primary/unique key values are kept in memory for the duration of a run, to guarantee referential integrity.
Development
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pre-commit install
ruff check .
ruff format --check .
mypy --strict src
pytest -m "not integration" # unit tests, no database needed
docker compose up -d # starts a throwaway Postgres on :5433
pytest -m integration # end-to-end tests against a real database
docker compose down
See CONTRIBUTING.md for more.
License
MIT — see LICENSE.
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 sowdb-0.1.0.tar.gz.
File metadata
- Download URL: sowdb-0.1.0.tar.gz
- Upload date:
- Size: 24.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f53ae6cec76ff1b84e1880d84ac9989f5b8b0a4d9bf0efefab61cbacacde2fe1
|
|
| MD5 |
b0a82f45a87f41233d61d2296a18288b
|
|
| BLAKE2b-256 |
6456e2aaaf05761e15e7748b4d2c5f5b2714f7f3b1492291d3a39a1c2aa2c799
|
Provenance
The following attestation bundles were made for sowdb-0.1.0.tar.gz:
Publisher:
publish.yml on aldanlt/sowdb
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sowdb-0.1.0.tar.gz -
Subject digest:
f53ae6cec76ff1b84e1880d84ac9989f5b8b0a4d9bf0efefab61cbacacde2fe1 - Sigstore transparency entry: 2208160724
- Sigstore integration time:
-
Permalink:
aldanlt/sowdb@4d48c6a638144b77a9b92aef8e066646b33a84c2 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/aldanlt
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4d48c6a638144b77a9b92aef8e066646b33a84c2 -
Trigger Event:
release
-
Statement type:
File details
Details for the file sowdb-0.1.0-py3-none-any.whl.
File metadata
- Download URL: sowdb-0.1.0-py3-none-any.whl
- Upload date:
- Size: 21.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bb31a080da547f96a87bfcaa08a122ce2015802b681a65e40c41e6d91c4bcc67
|
|
| MD5 |
3cf506c4266ca9c79d61249b9cb242d4
|
|
| BLAKE2b-256 |
f2518969248e2de0be54482d3e1923d8c81512915012cf1f687b69ed68f1c0bf
|
Provenance
The following attestation bundles were made for sowdb-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on aldanlt/sowdb
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sowdb-0.1.0-py3-none-any.whl -
Subject digest:
bb31a080da547f96a87bfcaa08a122ce2015802b681a65e40c41e6d91c4bcc67 - Sigstore transparency entry: 2208160756
- Sigstore integration time:
-
Permalink:
aldanlt/sowdb@4d48c6a638144b77a9b92aef8e066646b33a84c2 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/aldanlt
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4d48c6a638144b77a9b92aef8e066646b33a84c2 -
Trigger Event:
release
-
Statement type: