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 `_:
.. code-block:: bash
pip install codegen_database
Or with `uv `_:
.. code-block:: bash
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.
.. _declarative extensions: https://docs.sqlalchemy.org/en/latest/orm/extensions/declarative/index.html
.. _Alembic documentation: https://alembic.sqlalchemy.org/en/latest/tutorial.html
``alembic.ini``
---------------
In your ``alembic.ini``, add a ``[logger_codegen_database]`` section to enable codegen_database's
debug output:
.. code-block:: ini
[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 :func:`codegen_database.alembic.register.alembic_hook` before importing
your models. This applies codegen_database's patches and registers its Alembic
extensions.
2. Call :func:`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.
.. code-block:: python
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
:class:`~codegen_database.declarative.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.
.. code-block:: python
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 :class:`~codegen_database.factory.dimension.simple.CodegenDatabaseSimple` (a single
table). Choose a different factory by setting ``__factory__`` on the
class. Plugin instances go in ``__plugins__``:
.. code-block:: python
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:
.. code-block:: python
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 :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, 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 :doc:`plugins` for a full explanation.
Custom PK type:
.. code-block:: python
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+):
.. code-block:: python
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
:class:`~codegen_database.config.CodegenDatabaseConfig`:
.. code-block:: python
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 :ref:`cookbook-models-in-app` for a full application example and
:doc:`declarative` 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 :ref:`imperative-style` for the details.
.. code-block:: python
from codegen_database.factory import CodegenDatabaseSimple
products = CodegenDatabaseSimple(
tablename="products",
schemaname="dim",
metadata=metadata,
schema_items=[
Column("name", String, nullable=False),
Column("description", Text),
],
)