Skip to main content

Support for custom DDL in Alembic autogenerate command.

Project description

Alembic Dumb DDL

tests codecov

A plugin for Alembic DB migration tool which add support for arbitrary DDL scripts in autogenerate command.

Why is it dumb?

Because it doesn't try to be smart. It's really hard to compare arbitrary DDL scripts in the database with their sources in the project folder, because the scripts can contain multiple DDL statements, they may use version-specific features of a DBMS and they can be written for different DBMSs.

Alembic Dumb DDL doesn't check the state of the objects in the database, it only checks if the source code of the script has changed, comparing to the previous revision. And that's why Alembic Dumb DDL supports all databases and any kind of DDL scripts.

Quick start

Step 1: save your DDL script in a file, and make sure that it overwrites the entities, instead of just creating them (e.g. starts with DROP ... IF EXISTS or similar construct for your DBMS).

-- myapp/scripts/last_month_orders.sql

DROP VIEW IF EXISTS last_month_orders;

CREATE VIEW last_month_orders AS
    SELECT *
    FROM orders
    WHERE order_date > current_date - interval '30 days';

Step 2: Wrap your script in a DDL class:

# myapp/models.py

from alembic_dddl import DDL
from pathlib import Path

SCRIPTS = Path(__file__).parent / "scripts"


def load_sql(filename: str) -> str:
    """Helper function to load the contents of a file from a `scripts` directory"""
    return (SCRIPTS / filename).read_text()


my_ddl = DDL(
    # will be used in revision filename
    name="last_month_orders",
    # DDL script SQL code, will be used in upgrade command
    sql=load_sql("last_month_orders.sql"),
    # Cleanup SQL code, will be used in the first downgrade command
    down_sql="DROP VIEW IF EXISTS last_month_orders;",
)

Step 3: Register your ddl in alembic's env.py:

# migrations/env.py

from myapp.models import my_ddl
from alembic_dddl import register_ddl

register_ddl(my_ddl)

# ...
# the rest of the env.py file

That's it, from now on the alembic autogenerate command will keep track of last_month_orders.sql, and if it changes — automatically add update code to your migration scripts to update your entities.

The first run of the autogenerate command:

$ alembic revision --autogenerate -m "last_month_orders"
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic_dddl.dddl] Loaded scripts location from config: migrations/versions/ddl
INFO  [alembic_dddl.dddl] Detected new DDL "last_month_orders"
  Generating /Users/user/Projects/Python/alembic_custom_ddl/example/migrations/versions/2024_01_08_0955-0c897e9399a9_last_month_orders.py ...  done

For more detailed example see Example Project.

Installation

You can install Alembic Dumb DDL from pip:

pip install alembic_dddl

How does it work

Take a look at the example project.

Alembic Dumb DDL stores revisions of your DDL scripts in a folder inside versions directory (by default the folder is called versions/ddl). When you run alembic revision --autogenerate command Alembic Dumb DDL checks if any new DDL scripts were added, or if any of the existing ones are changed. For each such changed script its copy will be saved in the versions/ddl folder. This is the state against which the new changes in DDL scripts will be detected.

Along with creating the revisioned copy of the changed or added DDL scripts, Alembic Dumb DDL will also add upgrade and downgrade commands into the migration script itself.

The upgrade command will look like this:

def upgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.run_ddl_script('2024_01_08_1045_order_details_060d60b5c278.sql')

run_ddl_script is an operation added to alembic by Alembic Dumb DDL. All it does is executes each statement in the script against the database.

There are two variations of the downgrade command.

For the new DDL scripts (without existing revisions) it will be copied from the DDL.down_sql, as you defined it:

def downgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.execute('DROP VIEW IF EXISTS order_details;')

If DDL script already existed and was changed in this revision, the downgrade command will look similar to the upgrade command, but for previous version of the script:

def downgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.run_ddl_script('2024_01_08_1016_order_details_af80846764cd.sql')

Because each DDL script is used for both upgrade and downgrade commands, it's important that the script is overwriting entities, not just creating them. i.e. it should start with DROP ... IF EXISTS or similar construct for your DBMS.

Configuration

Alembic Dumb DDL has several configuration options. To set them, add [alembic_dddl] section to alembic.ini.

Here are the options and their default values:

[alembic_dddl]
# where the revisioned copies of DDL scripts will be stored
scripts_location = migrations/versions/ddl
# use timestamps instead of datetime in revisioned scripts file format
use_timestamps = False
# whether the comments should be ignored when comparing DDL scripts
ignore_comments = False

Setting up Logging

Alembic Dumb DDL shows some useful log messages when the autogenerate command is running. To enable logging, add the following to your alembic.ini:

[loggers]
# add alembic_dddl to the `keys` option:
keys = root,sqlalchemy,alembic,alembic_dddl

# add the `logger_alembic` section:
[logger_alembic]
level = INFO
handlers =
qualname = alembic

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

alembic_dddl-0.1.0.tar.gz (12.0 kB view details)

Uploaded Source

Built Distribution

alembic_dddl-0.1.0-py3-none-any.whl (13.0 kB view details)

Uploaded Python 3

File details

Details for the file alembic_dddl-0.1.0.tar.gz.

File metadata

  • Download URL: alembic_dddl-0.1.0.tar.gz
  • Upload date:
  • Size: 12.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.7.1 CPython/3.10.13 Darwin/23.2.0

File hashes

Hashes for alembic_dddl-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e388eb259ebe8fe9838de03ad3ce03caedc1980b28c6ae2dd83e60bebc865eaf
MD5 387083932ffb3e751c3383f9a8cbf705
BLAKE2b-256 9f52cead7c1ea551f6e198a30efb7587e16e4016be8418210bc5b3b37d533ab5

See more details on using hashes here.

File details

Details for the file alembic_dddl-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: alembic_dddl-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 13.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.7.1 CPython/3.10.13 Darwin/23.2.0

File hashes

Hashes for alembic_dddl-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d8bf93dd565d98abc28f0edf2c8900cafdb38c2c7dce847c0cf2b3f4cf27aa53
MD5 cd522642ad953bc64de37e443e193b22
BLAKE2b-256 76e2b65fa717609f7ebb026c66c2b0e4036e1c97f33170ac0677a73348ce8a7b

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page