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), ], )