diff --git a/flask_migrate/__init__.py b/flask_migrate/__init__.py index d8a907b..3f1158b 100755 --- a/flask_migrate/__init__.py +++ b/flask_migrate/__init__.py @@ -1,4 +1,5 @@ import os +import argparse from flask import current_app from flask.ext.script import Manager from alembic import __version__ as __alembic_version__ @@ -17,8 +18,10 @@ def __init__(self, db, directory, **kwargs): @property def metadata(self): - """Backwards compatibility, in old releases app.extensions['migrate'] - was set to db, and env.py accessed app.extensions['migrate'].metadata""" + """ + Backwards compatibility, in old releases app.extensions['migrate'] + was set to db, and env.py accessed app.extensions['migrate'].metadata + """ return self.db.metadata @@ -39,14 +42,16 @@ def get_template_directory(self): return os.path.join(package_dir, 'templates') -def _get_config(directory, x_arg=None): +def _get_config(directory, x_arg=None, opts=None): if directory is None: directory = current_app.extensions['migrate'].directory config = Config(os.path.join(directory, 'alembic.ini')) config.set_main_option('script_location', directory) + if config.cmd_opts is None: + config.cmd_opts = argparse.Namespace() + for opt in opts or []: + setattr(config.cmd_opts, opt, True) if x_arg is not None: - if config.cmd_opts is None: - config.cmd_opts = lambda: None if not getattr(config.cmd_opts, 'x', None): setattr(config.cmd_opts, 'x', [x_arg]) else: @@ -143,7 +148,7 @@ def revision(directory=None, message=None, autogenerate=False, sql=False, def migrate(directory=None, message=None, sql=False, head='head', splice=False, branch_label=None, version_path=None, rev_id=None): """Alias for 'revision --autogenerate'""" - config = _get_config(directory) + config = _get_config(directory, opts=['autogenerate']) if alembic_version >= (0, 7, 0): command.revision(config, message, autogenerate=True, sql=sql, head=head, splice=splice, branch_label=branch_label, diff --git a/flask_migrate/templates/flask-multidb/env.py b/flask_migrate/templates/flask-multidb/env.py index a0a39a3..71659dc 100755 --- a/flask_migrate/templates/flask-multidb/env.py +++ b/flask_migrate/templates/flask-multidb/env.py @@ -87,9 +87,23 @@ def run_migrations_online(): """ + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.readthedocs.org/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if len(script.upgrade_ops_list) >= len(bind_names) + 1: + empty = True + for upgrade_ops in script.upgrade_ops_list: + if not upgrade_ops.is_empty(): + empty = False + if empty: + directives[:] = [] + logger.info('No changes in schema detected.') + # for the direct-to-DB use case, start a transaction on all # engines, then run all migrations, then commit all transactions. - engines = {'': {'engine': engine_from_config( config.get_section(config.config_ini_section), prefix='sqlalchemy.', @@ -117,7 +131,9 @@ def run_migrations_online(): connection=rec['connection'], upgrade_token="%s_upgrades" % name, downgrade_token="%s_downgrades" % name, - target_metadata=get_metadata(name) + target_metadata=get_metadata(name), + process_revision_directives=process_revision_directives, + **current_app.extensions['migrate'].configure_args ) context.run_migrations(engine_name=name) diff --git a/flask_migrate/templates/flask/env.py b/flask_migrate/templates/flask/env.py index 0a038e6..4593816 100755 --- a/flask_migrate/templates/flask/env.py +++ b/flask_migrate/templates/flask/env.py @@ -2,6 +2,7 @@ from alembic import context from sqlalchemy import engine_from_config, pool from logging.config import fileConfig +import logging # this is the Alembic Config object, which provides # access to the values within the .ini file in use. @@ -10,13 +11,15 @@ # Interpret the config file for Python logging. # This line sets up loggers basically. fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') # add your model's MetaData object here # for 'autogenerate' support # from myapp import mymodel # target_metadata = mymodel.Base.metadata from flask import current_app -config.set_main_option('sqlalchemy.url', current_app.config.get('SQLALCHEMY_DATABASE_URI')) +config.set_main_option('sqlalchemy.url', + current_app.config.get('SQLALCHEMY_DATABASE_URI')) target_metadata = current_app.extensions['migrate'].db.metadata # other values from the config, defined by the needs of env.py, @@ -51,6 +54,17 @@ def run_migrations_online(): and associate a connection with the context. """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.readthedocs.org/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + engine = engine_from_config(config.get_section(config.config_ini_section), prefix='sqlalchemy.', poolclass=pool.NullPool) @@ -58,6 +72,7 @@ def run_migrations_online(): connection = engine.connect() context.configure(connection=connection, target_metadata=target_metadata, + process_revision_directives=process_revision_directives, **current_app.extensions['migrate'].configure_args) try: @@ -70,4 +85,3 @@ def run_migrations_online(): run_migrations_offline() else: run_migrations_online() - diff --git a/tests/app.py b/tests/app.py index cfc8781..d926e85 100755 --- a/tests/app.py +++ b/tests/app.py @@ -1,3 +1,4 @@ +#!/bin/env python from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_script import Manager diff --git a/tests/app_multidb.py b/tests/app_multidb.py index 118115a..525f638 100755 --- a/tests/app_multidb.py +++ b/tests/app_multidb.py @@ -1,3 +1,4 @@ +#!/bin/env python from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_script import Manager