Skip to content

Instantly share code, notes, and snippets.

@utek
Last active October 13, 2023 16:07
Show Gist options
  • Star 58 You must be signed in to star a gist
  • Fork 10 You must be signed in to fork a gist
  • Save utek/6163250 to your computer and use it in GitHub Desktop.
Save utek/6163250 to your computer and use it in GitHub Desktop.
Define ignored tables in alembic.ini

Add this in your ini file:

[alembic:exclude]
tables = spatial_ref_sys

In env.py:

    import re 
    
    def exclude_tables_from_config(config_):
        tables_ = config_.get("tables", None)
        if tables_ is not None:
            tables = tables_.split(",")
        return tables
        
    # Changes from: https://gist.github.com/utek/6163250#gistcomment-3851168
    exclude_tables = re.sub(r"\s+", '',  # replace whitespace
                        config.get_main_option('exclude', '')).split(',')


    def include_object(object, name, type_, *args, **kwargs):
        return not (type_ == 'table' and name in exclude_tables)
     
    def run_migrations_offline():
        """Run migrations in 'offline' mode.
    
        This configures the context with just a URL
        and not an Engine, though an Engine is acceptable
        here as well.  By skipping the Engine creation
        we don't even need a DBAPI to be available.
    
        Calls to context.execute() here emit the given string to the
        script output.
    
        """
        # url = config.get_main_option("sqlalchemy.url")
        context.configure(url=engine.url, include_object=include_object)
    
        with context.begin_transaction():
            context.run_migrations()
    
    
    def run_migrations_online():
        """Run migrations in 'online' mode.
    
        In this scenario we need to create an Engine
        and associate a connection with the context.
    
        """
        if isinstance(engine, Engine):
            connection = engine.connect()
        else:
            raise Exception('Expected engine instance got %s instead' % type(engine))
    
        context.configure(
            connection=connection,
            target_metadata=target_metadata,
            include_object=include_object
        )
    
        try:
            with context.begin_transaction():
                context.run_migrations()
        finally:
            connection.close()
@wandonye
Copy link

Looks like this is the solution I was looking for. But I got this error: NameError: global name 'engine' is not defined.

@wandonye
Copy link

Probably some version discrepancy. This one works for me:
def run_migrations_offline():
"""Run migrations in 'offline' mode.

This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well.  By skipping the Engine creation
we don't even need a DBAPI to be available.

Calls to context.execute() here emit the given string to the
script output.

"""
url = config.get_main_option("sqlalchemy.url")
context.configure(url=url, include_object = include_object)

with context.begin_transaction():
    context.run_migrations()

def run_migrations_online():
"""Run migrations in 'online' mode.

In this scenario we need to create an Engine
and associate a connection with the context.

"""
engine = engine_from_config(
            config.get_section(config.config_ini_section),
            prefix='sqlalchemy.',
            poolclass=pool.NullPool)

connection = engine.connect()
context.configure(
            connection=connection,
            target_metadata=target_metadata,
            include_object = include_object
            )

try:
    with context.begin_transaction():
        context.run_migrations()
finally:
    connection.close()

@getadeo
Copy link

getadeo commented Mar 9, 2016

Hi,

Thanks for this. Big help.

@petrus-jvrensburg
Copy link

Thanks tor this 👍

@peterlada
Copy link

👍

@kopf
Copy link

kopf commented Jan 3, 2017

You can get rid of the first function altogether by just doing

exclude_tables = config.get_section('alembic:exclude').get('tables', '').split(',')

@qholnessMD
Copy link

This is great!
Thank you.

@jdheywood
Copy link

Awesome 💯 thanks

@vincent-prz
Copy link

Super useful, thanks!

@JWDobken
Copy link

Very helpful!

@javiergarciad
Copy link

Very Helpful Kudos !! 👍 👍

@utek
Copy link
Author

utek commented Jun 2, 2020

After 6 years this gist is still being used :) Thank you :)

@juanuys
Copy link

juanuys commented Nov 12, 2020

If you want to use it in the main config section, and want to support YAML lists:

[alembic]
exclude_tables =
    quux
    eggs, foo,
    ham, bar
    baz
def get_list_from_config(config, key):
    arr = config.get_main_option(key, [])
    if arr:
        # split on newlines and commas, then trim (I mean strip)
        arr = [token for a in arr.split('\n') for b in a.split(',') if (token := b.strip())]
    return arr


exclude_tables = get_list_from_config(config, "exclude_tables")

To get

['quux', 'eggs', 'foo', 'ham', 'bar', 'baz']

@wasdee
Copy link

wasdee commented Nov 21, 2020

If you want to use it in the main config section, and want to support YAML lists:

[alembic]
exclude_tables =
    quux
    eggs, foo,
    ham, bar
    baz
def get_list_from_config(config, key):
    arr = config.get_main_option(key, None)
    if arr is not None:
        # split on newlines and commas, then trim (I mean strip)
        arr = [token for a in arr.split('\n') for b in a.split(',') if (token := b.strip())]
    return arr


exclude_tables = get_list_from_config(config, "exclude_tables")

To get

['quux', 'eggs', 'foo', 'ham', 'bar', 'baz']

+1

@Fingel
Copy link

Fingel commented Dec 2, 2020

I've found that both alembic.ini and the first few functions and calls in the gist can be greatly simplified:

In alembic ini simply add a value in the main config section (by sqlalchemy.url, for example):

# Exclude the following tables from being detected for migrations
exclude = spatial_ref_sys

And in env.py all you need is:

exclude_tables = config.get_main_option('exclude', '').split(',')


def include_object(object, name, type_, *args, **kwargs):
    if type_ == 'table' and name in exclude_tables:
        return False
    else:
        return True

Then just edit run_migrations_offline and run_migrations_online to use the newly defined include_object function just like the original gist.

@infnetdanpro
Copy link

Still working perfectly, thanks!

@mskoric
Copy link

mskoric commented Aug 9, 2021

works perfectly, for a single exclude!

just a little optimization so this also works:

exclude = spatial_ref_sys, us_lex, us_rules, us_gaz

env.py

exclude_tables = re.sub(r"\s+", '',  # replace whitespace
                        config.get_main_option('exclude', '')).split(',')


def include_object(object, name, type_, *args, **kwargs):
    return not (type_ == 'table' and name in exclude_tables)

Copy link

ghost commented Nov 7, 2021

Elegant! Thanks @mskoric

@utek
Copy link
Author

utek commented Nov 14, 2021

@mskoric Thank you. I've update the gist with your changes.

@xoelop
Copy link

xoelop commented Jan 26, 2022

I had to make this change for exclude_tables to work:

exclude_tables = re.sub(
    r"\s+",
    "",
    config.get_section_option("alembic:exclude", "tables"),  # replace whitespace
).split(",")

Other than that, worked great. Thanks!

@sitebay
Copy link

sitebay commented Feb 22, 2022

@xoelop Thank you, I needed alembic:exclude" and not just "exclude"

@heathhenley
Copy link

Another approach is given in the docs if instead of explicitly excluding tables, you would for example prefer to include tables only defined in the model (which turned out to my reason ending up here 😀):

target_metadata = MyModel.metadata

def include_name(name, type_, parent_names):
    if type_ == "table":
        return name in target_metadata.tables
    else:
        return True

and then passed to context in the run functions as before:

context.configure(
    # ...
    target_metadata = target_metadata,
    include_name = include_name,
    include_schemas = False
)

@ohing504
Copy link

ohing504 commented Oct 25, 2022

@heathhenley Simple! and working perfectly for me. Thanks👍

@hergerr
Copy link

hergerr commented Nov 18, 2022

@heathhenley Works like a charm, thanks!

@A-Asror
Copy link

A-Asror commented May 25, 2023

thanks, everything worked 👍👍

@danielshtel
Copy link

In addition to @heathhenley comment, you need to configure context in do_run_migrations, which will be executed in run_migrations online.

def do_run_migrations(connection: Connection) -> None:
    context.configure(
        connection=connection,
        target_metadata=target_metadata,
        include_name=include_name,
        include_schemas=False
    )

    with context.begin_transaction():
        context.run_migrations()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment