Setting up a new project¶
This guide walks through integrating pgcraft into a new project that uses Alembic for database migrations and SQLAlchemy for models.
Installation¶
pgcraft is available on PyPI:
pip install pgcraft
Or with uv:
uv add pgcraft
Dependencies¶
pgcraft installs SQLAlchemy and its declarative extensions automatically. You will also need Alembic for migrations. See the Alembic documentation for a full project setup guide.
alembic.ini¶
In your alembic.ini, add a [logger_pgcraft] section to enable pgcraft’s
debug output:
[loggers]
keys = root,sqlalchemy,alembic,pgcraft
[logger_pgcraft]
level = DEBUG
handlers = console
qualname = pgcraft
propagate = 0
env.py¶
Make three pgcraft-specific additions to migrations/env.py:
Call
pgcraft.alembic.register.pgcraft_alembic_hook()before importing your models. This applies pgcraft’s patches and registers its Alembic extensions.Call
pgcraft.alembic.register.pgcraft_configure_metadata()after loading your models/metadata. This registers schemas, roles, and grants.Pass
pgcraft_process_revision_directivesto bothcontext.configure()calls. This enables pgcraft’s autogenerate extensions, including dependency ordering of operations within each generated migration.
from pgcraft.alembic.register import (
pgcraft_alembic_hook,
pgcraft_configure_metadata,
pgcraft_process_revision_directives,
)
pgcraft_alembic_hook()
# ... your existing env.py setup (loading config, metadata, etc.) ...
pgcraft_configure_metadata(target_metadata, config=config)
def run_migrations_offline() -> None:
context.configure(
# ... your existing options ...
process_revision_directives=pgcraft_process_revision_directives,
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
with connectable.connect() as connection:
context.configure(
# ... your existing options ...
process_revision_directives=pgcraft_process_revision_directives,
)
with context.begin_transaction():
context.run_migrations()
models.py¶
Pass your MetaData instance to a pgcraft dimension factory so that
generated tables are registered for autogenerate detection.
Simple dimension (single table)¶
from sqlalchemy import Column, MetaData, String, Text
from pgcraft.factory import PGCraftSimple
metadata = MetaData()
products = PGCraftSimple(
tablename="products",
schemaname="dim",
metadata=metadata,
schema_items=[
Column("name", String, nullable=False),
Column("description", Text),
],
)
Append-only dimension (SCD Type 2)¶
An append-only dimension keeps a full history of attribute changes. The current state is always the most recent row in the attributes log:
from sqlalchemy import Column, MetaData, Numeric, String
from pgcraft.factory import PGCraftAppendOnly
prices = PGCraftAppendOnly(
tablename="prices",
schemaname="dim",
metadata=metadata,
schema_items=[
Column("sku", String, nullable=False),
Column(
"amount", Numeric(10, 2), nullable=False,
),
Column("currency", String(3), nullable=False),
],
)
Ledger (append-only value table)¶
A ledger stores immutable entries with a value column, an
entry_id UUID for correlating related entries, and dimension
columns:
from sqlalchemy import Column, MetaData, String
from pgcraft.factory import PGCraftLedger
order_events = PGCraftLedger(
tablename="order_events",
schemaname="ops",
metadata=metadata,
schema_items=[
Column("order_id", String, nullable=False),
Column("status", String, nullable=False),
],
)
See Ledger tables for balance views, double-entry enforcement, and numeric value types.
EAV dimension (sparse / dynamic attributes)¶
An EAV dimension stores each attribute as a separate row, making it efficient when rows have many nullable fields or when attributes are added frequently:
from sqlalchemy import (
Boolean, Column, Integer, MetaData, String,
)
from pgcraft.factory import PGCraftEAV
features = PGCraftEAV(
tablename="features",
schemaname="dim",
metadata=metadata,
schema_items=[
Column("name", String, nullable=False),
Column("enabled", Boolean),
Column("max_seats", Integer),
],
)
Customising factory behaviour¶
Any factory argument can be changed by passing a custom plugin list. See Plugin architecture for a full explanation.
Custom PK type:
from pgcraft.factory import PGCraftSimple
from pgcraft.plugins.pk import UUIDV4PKPlugin
products = PGCraftSimple(
"products", "dim", metadata, schema_items,
plugins=[UUIDV4PKPlugin()],
)
UUIDv7 primary key (PostgreSQL 18+):
from pgcraft.plugins.pk import UUIDV7PKPlugin
products = PGCraftSimple(
"products", "dim", metadata, schema_items,
plugins=[UUIDV7PKPlugin()],
)
Apply a custom plugin to every factory via
PGCraftConfig:
from pgcraft.config import PGCraftConfig
pgcraft_cfg = PGCraftConfig()
pgcraft_cfg.register(
TimestampPlugin(), TenantPlugin(),
)
PGCraftSimple(
"products", "dim", metadata, schema_items,
config=pgcraft_cfg,
)
PGCraftAppendOnly(
"orders", "dim", metadata, schema_items,
config=pgcraft_cfg,
)
To add PostgREST API views, see Extension system.