Setting up a new project

This guide walks through integrating codegen_database into a new project that uses Alembic for database migrations and SQLAlchemy for models.

Installation

codegen_database is available on PyPI:

pip install codegen_database

Or with uv:

uv add codegen_database

Dependencies

codegen_database 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_codegen_database] section to enable codegen_database’s debug output:

[loggers]
keys = root,sqlalchemy,alembic,codegen_database

[logger_codegen_database]
level = DEBUG
handlers = console
qualname = codegen_database
propagate = 0

env.py

Make three codegen_database-specific additions to migrations/env.py:

  1. Call codegen_database.alembic.register.alembic_hook() before importing your models. This applies codegen_database’s patches and registers its Alembic extensions.

  2. Call codegen_database.alembic.register.configure_metadata() after loading your models/metadata. This registers schemas, roles, and grants.

  3. Pass process_revision_directives and render_item to both context.configure() calls. The former enables dependency ordering of operations within each generated migration; the latter ensures codegen_database custom column types (e.g. TextEnum, IntEnum) are rendered as their underlying SQL types so that migrations do not require codegen_database at runtime.

from codegen_database.alembic.register import (
    alembic_hook,
    configure_metadata,
    process_revision_directives,
    render_item,
)

alembic_hook()

# ... your existing env.py setup (loading config, metadata, etc.) ...

configure_metadata(target_metadata, config=config)

def run_migrations_offline() -> None:
    context.configure(
        # ... your existing options ...
        process_revision_directives=process_revision_directives,
        render_item=render_item,
    )
    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=process_revision_directives,
            render_item=render_item,
        )
        with context.begin_transaction():
            context.run_migrations()

models.py

The recommended style is declarative: subclass CodegenDatabaseBase once to create a project-level base, then inherit from it for each model. The plugin pipeline runs automatically when each class body is executed and the generated table is registered on the metadata for Alembic autogeneration.

from sqlalchemy import Column, MetaData, String, Text
from codegen_database import CodegenDatabaseBase, construct_naming_conventions_dict

metadata = MetaData(
    naming_convention=construct_naming_conventions_dict(),
)


class Base(CodegenDatabaseBase):
    metadata = metadata


class Products(Base):
    __tablename__ = "products"
    __table_args__ = {"schema": "dim"}

    name = Column(String, nullable=False)
    description = Column(Text)

This creates:

  • dim.products — the dimension table

The default factory is CodegenDatabaseSimple (a single table). Choose a different factory by setting __factory__ on the class. Plugin instances go in __plugins__:

from sqlalchemy import Column, Numeric, String
from codegen_database.factory import CodegenDatabaseAppendOnly

class Prices(Base):
    __tablename__ = "prices"
    __table_args__ = {"schema": "dim"}
    __factory__ = CodegenDatabaseAppendOnly

    sku = Column(String, nullable=False)
    amount = Column(Numeric(10, 2), nullable=False)
    currency = Column(Text, nullable=False)

This creates:

  • dim.prices_root — entity root table with PK and created_at

  • dim.prices_attributes — append-only attributes log

  • dim.prices — a view joining root and attributes to show the current state

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, String
from codegen_database.factory.ledger import CodegenDatabaseLedger

class OrderEvents(Base):
    __tablename__ = "order_events"
    __table_args__ = {"schema": "ops"}
    __factory__ = CodegenDatabaseLedger

    order_id = Column(String, nullable=False)
    status = Column(String, nullable=False)

This creates:

  • ops.order_events — the append-only ledger table

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, String
from codegen_database.factory import CodegenDatabaseEAV

class Features(Base):
    __tablename__ = "features"
    __table_args__ = {"schema": "dim"}
    __factory__ = CodegenDatabaseEAV

    name = Column(String, nullable=False)
    enabled = Column(Boolean)
    max_seats = Column(Integer)

This creates:

  • dim.features_entity — entity table with PK and created_at

  • dim.features_attribute — attribute key/value rows

  • dim.features — a view pivoting attributes into columns

Customising factory behaviour

Any factory argument can be changed by adding plugins to __plugins__. See Plugin architecture for a full explanation.

Custom PK type:

from codegen_database.plugins.pk import UUIDV4PKPlugin

class Products(Base):
    __tablename__ = "products"
    __table_args__ = {"schema": "dim"}
    __plugins__ = [UUIDV4PKPlugin()]

    name = Column(String, nullable=False)

UUIDv7 primary key (PostgreSQL 18+):

from codegen_database.plugins.pk import UUIDV7PKPlugin

class Products(Base):
    __tablename__ = "products"
    __table_args__ = {"schema": "dim"}
    __plugins__ = [UUIDV7PKPlugin()]

    name = Column(String, nullable=False)

Apply a custom plugin to every factory via CodegenDatabaseConfig:

from codegen_database.config import CodegenDatabaseConfig

codegen_database_cfg = CodegenDatabaseConfig()
codegen_database_cfg.register(TimestampPlugin(), TenantPlugin())
metadata.info["codegen_database_config"] = codegen_database_cfg

Every model on Base now picks up the global plugins automatically.

Key rules:

  • metadata and codegen_database_config are set on your Base class and inherited by all models.

  • __factory__ and __plugins__ are read from the model’s own __dict__ only — they are not inherited from parent classes.

  • Classes without __tablename__ are skipped automatically, so shared column mixins work as expected.

  • After the class body runs, Products.__table__ holds the generated SQLAlchemy Table and the class is ORM-mapped.

See Using codegen_database models in your application for a full application example and Declarative style for the full reference.

Imperative alternative

codegen_database also supports calling factory functions directly. This form is useful for programmatic construction (e.g. looping over a config dict). See Kitchen-sink imperative example for the details.

from codegen_database.factory import CodegenDatabaseSimple

products = CodegenDatabaseSimple(
    tablename="products",
    schemaname="dim",
    metadata=metadata,
    schema_items=[
        Column("name", String, nullable=False),
        Column("description", Text),
    ],
)