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:
Call
codegen_database.alembic.register.alembic_hook()before importing your models. This applies codegen_database’s patches and registers its Alembic extensions.Call
codegen_database.alembic.register.configure_metadata()after loading your models/metadata. This registers schemas, roles, and grants.Pass
process_revision_directivesandrender_itemto bothcontext.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 andcreated_atdim.prices_attributes— append-only attributes logdim.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 andcreated_atdim.features_attribute— attribute key/value rowsdim.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:
metadataandcodegen_database_configare set on yourBaseclass 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 SQLAlchemyTableand 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),
],
)