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 `_: .. code-block:: bash pip install pgcraft Or with `uv `_: .. code-block:: bash 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 documentation: https://alembic.sqlalchemy.org/en/latest/tutorial.html ``alembic.ini`` --------------- In your ``alembic.ini``, add a ``[logger_pgcraft]`` section to enable pgcraft's debug output: .. code-block:: ini [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``: 1. Call :func:`pgcraft.alembic.register.pgcraft_alembic_hook` before importing your models. This applies pgcraft's patches and registers its Alembic extensions. 2. Call :func:`pgcraft.alembic.register.pgcraft_configure_metadata` after loading your models/metadata. This registers schemas, roles, and grants. 3. Pass ``pgcraft_process_revision_directives`` to both ``context.configure()`` calls. This enables pgcraft's autogenerate extensions, including dependency ordering of operations within each generated migration. .. code-block:: python 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) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python 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: .. code-block:: python 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: .. code-block:: python 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 :doc:`ledgers` 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: .. code-block:: python 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 :doc:`plugins` for a full explanation. Custom PK type: .. code-block:: python 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+): .. code-block:: python from pgcraft.plugins.pk import UUIDV7PKPlugin products = PGCraftSimple( "products", "dim", metadata, schema_items, plugins=[UUIDV7PKPlugin()], ) Apply a custom plugin to every factory via :class:`~pgcraft.config.PGCraftConfig`: .. code-block:: python 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 :doc:`extensions`.