summaryrefslogtreecommitdiffhomepage
path: root/libs/flask_migrate
diff options
context:
space:
mode:
authormorpheus65535 <[email protected]>2023-07-26 19:34:49 -0400
committerGitHub <[email protected]>2023-07-26 19:34:49 -0400
commitbccded275c3cb09dc001d66858f3200c78723935 (patch)
tree14f4409119ff7ca8af5f4827dca249bc0b25f768 /libs/flask_migrate
parent486d2f9481982fef0ff0a30c314f74e9268cc7fd (diff)
downloadbazarr-bccded275c3cb09dc001d66858f3200c78723935.tar.gz
bazarr-bccded275c3cb09dc001d66858f3200c78723935.zip
Replaced peewee with sqlalchemy as ORM. This is a major change, please report related issues on Discord.v1.2.5-beta.3
Diffstat (limited to 'libs/flask_migrate')
-rw-r--r--libs/flask_migrate/__init__.py266
-rw-r--r--libs/flask_migrate/cli.py251
-rw-r--r--libs/flask_migrate/templates/aioflask-multidb/README1
-rw-r--r--libs/flask_migrate/templates/aioflask-multidb/alembic.ini.mako50
-rw-r--r--libs/flask_migrate/templates/aioflask-multidb/env.py199
-rw-r--r--libs/flask_migrate/templates/aioflask-multidb/script.py.mako53
-rw-r--r--libs/flask_migrate/templates/aioflask/README1
-rw-r--r--libs/flask_migrate/templates/aioflask/alembic.ini.mako50
-rw-r--r--libs/flask_migrate/templates/aioflask/env.py115
-rw-r--r--libs/flask_migrate/templates/aioflask/script.py.mako24
-rw-r--r--libs/flask_migrate/templates/flask-multidb/README1
-rw-r--r--libs/flask_migrate/templates/flask-multidb/alembic.ini.mako50
-rw-r--r--libs/flask_migrate/templates/flask-multidb/env.py188
-rw-r--r--libs/flask_migrate/templates/flask-multidb/script.py.mako53
-rw-r--r--libs/flask_migrate/templates/flask/README1
-rw-r--r--libs/flask_migrate/templates/flask/alembic.ini.mako50
-rw-r--r--libs/flask_migrate/templates/flask/env.py110
-rw-r--r--libs/flask_migrate/templates/flask/script.py.mako24
18 files changed, 1487 insertions, 0 deletions
diff --git a/libs/flask_migrate/__init__.py b/libs/flask_migrate/__init__.py
new file mode 100644
index 000000000..c61b7f5eb
--- /dev/null
+++ b/libs/flask_migrate/__init__.py
@@ -0,0 +1,266 @@
+import argparse
+from functools import wraps
+import logging
+import os
+import sys
+from flask import current_app
+from alembic import __version__ as __alembic_version__
+from alembic.config import Config as AlembicConfig
+from alembic import command
+from alembic.util import CommandError
+
+alembic_version = tuple([int(v) for v in __alembic_version__.split('.')[0:3]])
+log = logging.getLogger(__name__)
+
+
+class _MigrateConfig(object):
+ def __init__(self, migrate, db, **kwargs):
+ self.migrate = migrate
+ self.db = db
+ self.directory = migrate.directory
+ self.configure_args = 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
+ """
+ return self.db.metadata
+
+
+class Config(AlembicConfig):
+ def __init__(self, *args, **kwargs):
+ self.template_directory = kwargs.pop('template_directory', None)
+ super().__init__(*args, **kwargs)
+
+ def get_template_directory(self):
+ if self.template_directory:
+ return self.template_directory
+ package_dir = os.path.abspath(os.path.dirname(__file__))
+ return os.path.join(package_dir, 'templates')
+
+
+class Migrate(object):
+ def __init__(self, app=None, db=None, directory='migrations', command='db',
+ compare_type=True, render_as_batch=True, **kwargs):
+ self.configure_callbacks = []
+ self.db = db
+ self.command = command
+ self.directory = str(directory)
+ self.alembic_ctx_kwargs = kwargs
+ self.alembic_ctx_kwargs['compare_type'] = compare_type
+ self.alembic_ctx_kwargs['render_as_batch'] = render_as_batch
+ if app is not None and db is not None:
+ self.init_app(app, db, directory)
+
+ def init_app(self, app, db=None, directory=None, command=None,
+ compare_type=None, render_as_batch=None, **kwargs):
+ self.db = db or self.db
+ self.command = command or self.command
+ self.directory = str(directory or self.directory)
+ self.alembic_ctx_kwargs.update(kwargs)
+ if compare_type is not None:
+ self.alembic_ctx_kwargs['compare_type'] = compare_type
+ if render_as_batch is not None:
+ self.alembic_ctx_kwargs['render_as_batch'] = render_as_batch
+ if not hasattr(app, 'extensions'):
+ app.extensions = {}
+ app.extensions['migrate'] = _MigrateConfig(
+ self, self.db, **self.alembic_ctx_kwargs)
+
+ from flask_migrate.cli import db as db_cli_group
+ app.cli.add_command(db_cli_group, name=self.command)
+
+ def configure(self, f):
+ self.configure_callbacks.append(f)
+ return f
+
+ def call_configure_callbacks(self, config):
+ for f in self.configure_callbacks:
+ config = f(config)
+ return config
+
+ def get_config(self, directory=None, x_arg=None, opts=None):
+ if directory is None:
+ directory = self.directory
+ directory = str(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 not hasattr(config.cmd_opts, 'x'):
+ if x_arg is not None:
+ setattr(config.cmd_opts, 'x', [])
+ if isinstance(x_arg, list) or isinstance(x_arg, tuple):
+ for x in x_arg:
+ config.cmd_opts.x.append(x)
+ else:
+ config.cmd_opts.x.append(x_arg)
+ else:
+ setattr(config.cmd_opts, 'x', None)
+ return self.call_configure_callbacks(config)
+
+
+def catch_errors(f):
+ @wraps(f)
+ def wrapped(*args, **kwargs):
+ try:
+ f(*args, **kwargs)
+ except (CommandError, RuntimeError) as exc:
+ log.error('Error: ' + str(exc))
+ sys.exit(1)
+ return wrapped
+
+
+@catch_errors
+def list_templates():
+ """List available templates."""
+ config = Config()
+ config.print_stdout("Available templates:\n")
+ for tempname in sorted(os.listdir(config.get_template_directory())):
+ with open(
+ os.path.join(config.get_template_directory(), tempname, "README")
+ ) as readme:
+ synopsis = next(readme).strip()
+ config.print_stdout("%s - %s", tempname, synopsis)
+
+
+@catch_errors
+def init(directory=None, multidb=False, template=None, package=False):
+ """Creates a new migration repository"""
+ if directory is None:
+ directory = current_app.extensions['migrate'].directory
+ template_directory = None
+ if template is not None and ('/' in template or '\\' in template):
+ template_directory, template = os.path.split(template)
+ config = Config(template_directory=template_directory)
+ config.set_main_option('script_location', directory)
+ config.config_file_name = os.path.join(directory, 'alembic.ini')
+ config = current_app.extensions['migrate'].\
+ migrate.call_configure_callbacks(config)
+ if multidb and template is None:
+ template = 'flask-multidb'
+ elif template is None:
+ template = 'flask'
+ command.init(config, directory, template=template, package=package)
+
+
+@catch_errors
+def revision(directory=None, message=None, autogenerate=False, sql=False,
+ head='head', splice=False, branch_label=None, version_path=None,
+ rev_id=None):
+ """Create a new revision file."""
+ opts = ['autogenerate'] if autogenerate else None
+ config = current_app.extensions['migrate'].migrate.get_config(
+ directory, opts=opts)
+ command.revision(config, message, autogenerate=autogenerate, sql=sql,
+ head=head, splice=splice, branch_label=branch_label,
+ version_path=version_path, rev_id=rev_id)
+
+
+@catch_errors
+def migrate(directory=None, message=None, sql=False, head='head', splice=False,
+ branch_label=None, version_path=None, rev_id=None, x_arg=None):
+ """Alias for 'revision --autogenerate'"""
+ config = current_app.extensions['migrate'].migrate.get_config(
+ directory, opts=['autogenerate'], x_arg=x_arg)
+ command.revision(config, message, autogenerate=True, sql=sql,
+ head=head, splice=splice, branch_label=branch_label,
+ version_path=version_path, rev_id=rev_id)
+
+
+@catch_errors
+def edit(directory=None, revision='current'):
+ """Edit current revision."""
+ if alembic_version >= (0, 8, 0):
+ config = current_app.extensions['migrate'].migrate.get_config(
+ directory)
+ command.edit(config, revision)
+ else:
+ raise RuntimeError('Alembic 0.8.0 or greater is required')
+
+
+@catch_errors
+def merge(directory=None, revisions='', message=None, branch_label=None,
+ rev_id=None):
+ """Merge two revisions together. Creates a new migration file"""
+ config = current_app.extensions['migrate'].migrate.get_config(directory)
+ command.merge(config, revisions, message=message,
+ branch_label=branch_label, rev_id=rev_id)
+
+
+@catch_errors
+def upgrade(directory=None, revision='head', sql=False, tag=None, x_arg=None):
+ """Upgrade to a later version"""
+ config = current_app.extensions['migrate'].migrate.get_config(directory,
+ x_arg=x_arg)
+ command.upgrade(config, revision, sql=sql, tag=tag)
+
+
+@catch_errors
+def downgrade(directory=None, revision='-1', sql=False, tag=None, x_arg=None):
+ """Revert to a previous version"""
+ config = current_app.extensions['migrate'].migrate.get_config(directory,
+ x_arg=x_arg)
+ if sql and revision == '-1':
+ revision = 'head:-1'
+ command.downgrade(config, revision, sql=sql, tag=tag)
+
+
+@catch_errors
+def show(directory=None, revision='head'):
+ """Show the revision denoted by the given symbol."""
+ config = current_app.extensions['migrate'].migrate.get_config(directory)
+ command.show(config, revision)
+
+
+@catch_errors
+def history(directory=None, rev_range=None, verbose=False,
+ indicate_current=False):
+ """List changeset scripts in chronological order."""
+ config = current_app.extensions['migrate'].migrate.get_config(directory)
+ if alembic_version >= (0, 9, 9):
+ command.history(config, rev_range, verbose=verbose,
+ indicate_current=indicate_current)
+ else:
+ command.history(config, rev_range, verbose=verbose)
+
+
+@catch_errors
+def heads(directory=None, verbose=False, resolve_dependencies=False):
+ """Show current available heads in the script directory"""
+ config = current_app.extensions['migrate'].migrate.get_config(directory)
+ command.heads(config, verbose=verbose,
+ resolve_dependencies=resolve_dependencies)
+
+
+@catch_errors
+def branches(directory=None, verbose=False):
+ """Show current branch points"""
+ config = current_app.extensions['migrate'].migrate.get_config(directory)
+ command.branches(config, verbose=verbose)
+
+
+@catch_errors
+def current(directory=None, verbose=False):
+ """Display the current revision for each database."""
+ config = current_app.extensions['migrate'].migrate.get_config(directory)
+ command.current(config, verbose=verbose)
+
+
+@catch_errors
+def stamp(directory=None, revision='head', sql=False, tag=None):
+ """'stamp' the revision table with the given revision; don't run any
+ migrations"""
+ config = current_app.extensions['migrate'].migrate.get_config(directory)
+ command.stamp(config, revision, sql=sql, tag=tag)
+
+
+@catch_errors
+def check(directory=None):
+ """Check if there are any new operations to migrate"""
+ config = current_app.extensions['migrate'].migrate.get_config(directory)
+ command.check(config)
diff --git a/libs/flask_migrate/cli.py b/libs/flask_migrate/cli.py
new file mode 100644
index 000000000..176672c58
--- /dev/null
+++ b/libs/flask_migrate/cli.py
@@ -0,0 +1,251 @@
+import click
+from flask.cli import with_appcontext
+from flask_migrate import list_templates as _list_templates
+from flask_migrate import init as _init
+from flask_migrate import revision as _revision
+from flask_migrate import migrate as _migrate
+from flask_migrate import edit as _edit
+from flask_migrate import merge as _merge
+from flask_migrate import upgrade as _upgrade
+from flask_migrate import downgrade as _downgrade
+from flask_migrate import show as _show
+from flask_migrate import history as _history
+from flask_migrate import heads as _heads
+from flask_migrate import branches as _branches
+from flask_migrate import current as _current
+from flask_migrate import stamp as _stamp
+from flask_migrate import check as _check
+
+
+def db():
+ """Perform database migrations."""
+ pass
+
+
+@with_appcontext
+def list_templates():
+ """List available templates."""
+ _list_templates()
+
+
[email protected]('-d', '--directory', default=None,
+ help=('Migration script directory (default is "migrations")'))
[email protected]('--multidb', is_flag=True,
+ help=('Support multiple databases'))
[email protected]('-t', '--template', default=None,
+ help=('Repository template to use (default is "flask")'))
[email protected]('--package', is_flag=True,
+ help=('Write empty __init__.py files to the environment and '
+ 'version locations'))
+@with_appcontext
+def init(directory, multidb, template, package):
+ """Creates a new migration repository."""
+ _init(directory, multidb, template, package)
+
+
[email protected]('-d', '--directory', default=None,
+ help=('Migration script directory (default is "migrations")'))
[email protected]('-m', '--message', default=None, help='Revision message')
[email protected]('--autogenerate', is_flag=True,
+ help=('Populate revision script with candidate migration '
+ 'operations, based on comparison of database to model'))
[email protected]('--sql', is_flag=True,
+ help=('Don\'t emit SQL to database - dump to standard output '
+ 'instead'))
[email protected]('--head', default='head',
+ help=('Specify head revision or <branchname>@head to base new '
+ 'revision on'))
[email protected]('--splice', is_flag=True,
+ help=('Allow a non-head revision as the "head" to splice onto'))
[email protected]('--branch-label', default=None,
+ help=('Specify a branch label to apply to the new revision'))
[email protected]('--version-path', default=None,
+ help=('Specify specific path from config for version file'))
[email protected]('--rev-id', default=None,
+ help=('Specify a hardcoded revision id instead of generating '
+ 'one'))
+@with_appcontext
+def revision(directory, message, autogenerate, sql, head, splice, branch_label,
+ version_path, rev_id):
+ """Create a new revision file."""
+ _revision(directory, message, autogenerate, sql, head, splice,
+ branch_label, version_path, rev_id)
+
+
[email protected]('-d', '--directory', default=None,
+ help=('Migration script directory (default is "migrations")'))
[email protected]('-m', '--message', default=None, help='Revision message')
[email protected]('--sql', is_flag=True,
+ help=('Don\'t emit SQL to database - dump to standard output '
+ 'instead'))
[email protected]('--head', default='head',
+ help=('Specify head revision or <branchname>@head to base new '
+ 'revision on'))
[email protected]('--splice', is_flag=True,
+ help=('Allow a non-head revision as the "head" to splice onto'))
[email protected]('--branch-label', default=None,
+ help=('Specify a branch label to apply to the new revision'))
[email protected]('--version-path', default=None,
+ help=('Specify specific path from config for version file'))
[email protected]('--rev-id', default=None,
+ help=('Specify a hardcoded revision id instead of generating '
+ 'one'))
[email protected]('-x', '--x-arg', multiple=True,
+ help='Additional arguments consumed by custom env.py scripts')
+@with_appcontext
+def migrate(directory, message, sql, head, splice, branch_label, version_path,
+ rev_id, x_arg):
+ """Autogenerate a new revision file (Alias for
+ 'revision --autogenerate')"""
+ _migrate(directory, message, sql, head, splice, branch_label, version_path,
+ rev_id, x_arg)
+
+
[email protected]('-d', '--directory', default=None,
+ help=('Migration script directory (default is "migrations")'))
[email protected]('revision', default='head')
+@with_appcontext
+def edit(directory, revision):
+ """Edit a revision file"""
+ _edit(directory, revision)
+
+
[email protected]('-d', '--directory', default=None,
+ help=('Migration script directory (default is "migrations")'))
[email protected]('-m', '--message', default=None, help='Merge revision message')
[email protected]('--branch-label', default=None,
+ help=('Specify a branch label to apply to the new revision'))
[email protected]('--rev-id', default=None,
+ help=('Specify a hardcoded revision id instead of generating '
+ 'one'))
[email protected]('revisions', nargs=-1)
+@with_appcontext
+def merge(directory, message, branch_label, rev_id, revisions):
+ """Merge two revisions together, creating a new revision file"""
+ _merge(directory, revisions, message, branch_label, rev_id)
+
+
[email protected]('-d', '--directory', default=None,
+ help=('Migration script directory (default is "migrations")'))
[email protected]('--sql', is_flag=True,
+ help=('Don\'t emit SQL to database - dump to standard output '
+ 'instead'))
[email protected]('--tag', default=None,
+ help=('Arbitrary "tag" name - can be used by custom env.py '
+ 'scripts'))
[email protected]('-x', '--x-arg', multiple=True,
+ help='Additional arguments consumed by custom env.py scripts')
[email protected]('revision', default='head')
+@with_appcontext
+def upgrade(directory, sql, tag, x_arg, revision):
+ """Upgrade to a later version"""
+ _upgrade(directory, revision, sql, tag, x_arg)
+
+
[email protected]('-d', '--directory', default=None,
+ help=('Migration script directory (default is "migrations")'))
[email protected]('--sql', is_flag=True,
+ help=('Don\'t emit SQL to database - dump to standard output '
+ 'instead'))
[email protected]('--tag', default=None,
+ help=('Arbitrary "tag" name - can be used by custom env.py '
+ 'scripts'))
[email protected]('-x', '--x-arg', multiple=True,
+ help='Additional arguments consumed by custom env.py scripts')
[email protected]('revision', default='-1')
+@with_appcontext
+def downgrade(directory, sql, tag, x_arg, revision):
+ """Revert to a previous version"""
+ _downgrade(directory, revision, sql, tag, x_arg)
+
+
[email protected]('-d', '--directory', default=None,
+ help=('Migration script directory (default is "migrations")'))
[email protected]('revision', default='head')
+@with_appcontext
+def show(directory, revision):
+ """Show the revision denoted by the given symbol."""
+ _show(directory, revision)
+
+
[email protected]('-d', '--directory', default=None,
+ help=('Migration script directory (default is "migrations")'))
[email protected]('-r', '--rev-range', default=None,
+ help='Specify a revision range; format is [start]:[end]')
[email protected]('-v', '--verbose', is_flag=True, help='Use more verbose output')
[email protected]('-i', '--indicate-current', is_flag=True,
+ help=('Indicate current version (Alembic 0.9.9 or greater is '
+ 'required)'))
+@with_appcontext
+def history(directory, rev_range, verbose, indicate_current):
+ """List changeset scripts in chronological order."""
+ _history(directory, rev_range, verbose, indicate_current)
+
+
[email protected]('-d', '--directory', default=None,
+ help=('Migration script directory (default is "migrations")'))
[email protected]('-v', '--verbose', is_flag=True, help='Use more verbose output')
[email protected]('--resolve-dependencies', is_flag=True,
+ help='Treat dependency versions as down revisions')
+@with_appcontext
+def heads(directory, verbose, resolve_dependencies):
+ """Show current available heads in the script directory"""
+ _heads(directory, verbose, resolve_dependencies)
+
+
[email protected]('-d', '--directory', default=None,
+ help=('Migration script directory (default is "migrations")'))
[email protected]('-v', '--verbose', is_flag=True, help='Use more verbose output')
+@with_appcontext
+def branches(directory, verbose):
+ """Show current branch points"""
+ _branches(directory, verbose)
+
+
[email protected]('-d', '--directory', default=None,
+ help=('Migration script directory (default is "migrations")'))
[email protected]('-v', '--verbose', is_flag=True, help='Use more verbose output')
+@with_appcontext
+def current(directory, verbose):
+ """Display the current revision for each database."""
+ _current(directory, verbose)
+
+
[email protected]('-d', '--directory', default=None,
+ help=('Migration script directory (default is "migrations")'))
[email protected]('--sql', is_flag=True,
+ help=('Don\'t emit SQL to database - dump to standard output '
+ 'instead'))
[email protected]('--tag', default=None,
+ help=('Arbitrary "tag" name - can be used by custom env.py '
+ 'scripts'))
[email protected]('revision', default='head')
+@with_appcontext
+def stamp(directory, sql, tag, revision):
+ """'stamp' the revision table with the given revision; don't run any
+ migrations"""
+ _stamp(directory, revision, sql, tag)
+
+
[email protected]('-d', '--directory', default=None,
+ help=('Migration script directory (default is "migrations")'))
+@with_appcontext
+def check(directory):
+ """Check if there are any new operations to migrate"""
+ _check(directory)
diff --git a/libs/flask_migrate/templates/aioflask-multidb/README b/libs/flask_migrate/templates/aioflask-multidb/README
new file mode 100644
index 000000000..02cce84ee
--- /dev/null
+++ b/libs/flask_migrate/templates/aioflask-multidb/README
@@ -0,0 +1 @@
+Multi-database configuration for aioflask.
diff --git a/libs/flask_migrate/templates/aioflask-multidb/alembic.ini.mako b/libs/flask_migrate/templates/aioflask-multidb/alembic.ini.mako
new file mode 100644
index 000000000..ec9d45c26
--- /dev/null
+++ b/libs/flask_migrate/templates/aioflask-multidb/alembic.ini.mako
@@ -0,0 +1,50 @@
+# A generic, single database configuration.
+
+[alembic]
+# template used to generate migration files
+# file_template = %%(rev)s_%%(slug)s
+
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
+
+# Logging configuration
+[loggers]
+keys = root,sqlalchemy,alembic,flask_migrate
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+qualname =
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+
+[logger_alembic]
+level = INFO
+handlers =
+qualname = alembic
+
+[logger_flask_migrate]
+level = INFO
+handlers =
+qualname = flask_migrate
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S
diff --git a/libs/flask_migrate/templates/aioflask-multidb/env.py b/libs/flask_migrate/templates/aioflask-multidb/env.py
new file mode 100644
index 000000000..299f544f5
--- /dev/null
+++ b/libs/flask_migrate/templates/aioflask-multidb/env.py
@@ -0,0 +1,199 @@
+import asyncio
+import logging
+from logging.config import fileConfig
+
+from sqlalchemy import MetaData
+from flask import current_app
+
+from alembic import context
+
+USE_TWOPHASE = False
+
+# this is the Alembic Config object, which provides
+# access to the values within the .ini file in use.
+config = context.config
+
+# Interpret the config file for Python logging.
+# This line sets up loggers basically.
+fileConfig(config.config_file_name)
+logger = logging.getLogger('alembic.env')
+
+
+def get_engine(bind_key=None):
+ try:
+ # this works with Flask-SQLAlchemy<3 and Alchemical
+ return current_app.extensions['migrate'].db.get_engine(bind=bind_key)
+ except TypeError:
+ # this works with Flask-SQLAlchemy>=3
+ return current_app.extensions['migrate'].db.engines.get(bind_key)
+
+
+def get_engine_url(bind_key=None):
+ try:
+ return get_engine(bind_key).url.render_as_string(
+ hide_password=False).replace('%', '%%')
+ except AttributeError:
+ return str(get_engine(bind_key).url).replace('%', '%%')
+
+
+# add your model's MetaData object here
+# for 'autogenerate' support
+# from myapp import mymodel
+# target_metadata = mymodel.Base.metadata
+config.set_main_option('sqlalchemy.url', get_engine_url())
+bind_names = []
+if current_app.config.get('SQLALCHEMY_BINDS') is not None:
+ bind_names = list(current_app.config['SQLALCHEMY_BINDS'].keys())
+else:
+ get_bind_names = getattr(current_app.extensions['migrate'].db,
+ 'bind_names', None)
+ if get_bind_names:
+ bind_names = get_bind_names()
+for bind in bind_names:
+ context.config.set_section_option(
+ bind, "sqlalchemy.url", get_engine_url(bind_key=bind))
+target_db = current_app.extensions['migrate'].db
+
+
+# other values from the config, defined by the needs of env.py,
+# can be acquired:
+# my_important_option = config.get_main_option("my_important_option")
+# ... etc.
+
+
+def get_metadata(bind):
+ """Return the metadata for a bind."""
+ if bind == '':
+ bind = None
+ if hasattr(target_db, 'metadatas'):
+ return target_db.metadatas[bind]
+
+ # legacy, less flexible implementation
+ m = MetaData()
+ for t in target_db.metadata.tables.values():
+ if t.info.get('bind_key') == bind:
+ t.tometadata(m)
+ return m
+
+
+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.
+
+ """
+ # for the --sql use case, run migrations for each URL into
+ # individual files.
+
+ engines = {
+ '': {
+ 'url': context.config.get_main_option('sqlalchemy.url')
+ }
+ }
+ for name in bind_names:
+ engines[name] = rec = {}
+ rec['url'] = context.config.get_section_option(name, "sqlalchemy.url")
+
+ for name, rec in engines.items():
+ logger.info("Migrating database %s" % (name or '<default>'))
+ file_ = "%s.sql" % name
+ logger.info("Writing output to %s" % file_)
+ with open(file_, 'w') as buffer:
+ context.configure(
+ url=rec['url'],
+ output_buffer=buffer,
+ target_metadata=get_metadata(name),
+ literal_binds=True,
+ )
+ with context.begin_transaction():
+ context.run_migrations(engine_name=name)
+
+
+def do_run_migrations(_, engines):
+ # this callback is used to prevent an auto-migration from being generated
+ # when there are no changes to the schema
+ # reference: http://alembic.zzzcomputing.com/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 name, rec in engines.items():
+ rec['sync_connection'] = conn = rec['connection']._sync_connection()
+ if USE_TWOPHASE:
+ rec['transaction'] = conn.begin_twophase()
+ else:
+ rec['transaction'] = conn.begin()
+
+ try:
+ for name, rec in engines.items():
+ logger.info("Migrating database %s" % (name or '<default>'))
+ context.configure(
+ connection=rec['sync_connection'],
+ upgrade_token="%s_upgrades" % name,
+ downgrade_token="%s_downgrades" % name,
+ target_metadata=get_metadata(name),
+ process_revision_directives=process_revision_directives,
+ **current_app.extensions['migrate'].configure_args
+ )
+ context.run_migrations(engine_name=name)
+
+ if USE_TWOPHASE:
+ for rec in engines.values():
+ rec['transaction'].prepare()
+
+ for rec in engines.values():
+ rec['transaction'].commit()
+ except: # noqa: E722
+ for rec in engines.values():
+ rec['transaction'].rollback()
+ raise
+ finally:
+ for rec in engines.values():
+ rec['sync_connection'].close()
+
+
+async 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.
+
+ """
+
+ # for the direct-to-DB use case, start a transaction on all
+ # engines, then run all migrations, then commit all transactions.
+ engines = {
+ '': {'engine': get_engine()}
+ }
+ for name in bind_names:
+ engines[name] = rec = {}
+ rec['engine'] = get_engine(bind_key=name)
+
+ for name, rec in engines.items():
+ engine = rec['engine']
+ rec['connection'] = await engine.connect().start()
+
+ await engines['']['connection'].run_sync(do_run_migrations, engines)
+
+ for rec in engines.values():
+ await rec['connection'].close()
+
+
+if context.is_offline_mode():
+ run_migrations_offline()
+else:
+ asyncio.get_event_loop().run_until_complete(run_migrations_online())
diff --git a/libs/flask_migrate/templates/aioflask-multidb/script.py.mako b/libs/flask_migrate/templates/aioflask-multidb/script.py.mako
new file mode 100644
index 000000000..3beabc463
--- /dev/null
+++ b/libs/flask_migrate/templates/aioflask-multidb/script.py.mako
@@ -0,0 +1,53 @@
+<%!
+import re
+
+%>"""${message}
+
+Revision ID: ${up_revision}
+Revises: ${down_revision | comma,n}
+Create Date: ${create_date}
+
+"""
+from alembic import op
+import sqlalchemy as sa
+${imports if imports else ""}
+
+# revision identifiers, used by Alembic.
+revision = ${repr(up_revision)}
+down_revision = ${repr(down_revision)}
+branch_labels = ${repr(branch_labels)}
+depends_on = ${repr(depends_on)}
+
+
+def upgrade(engine_name):
+ globals()["upgrade_%s" % engine_name]()
+
+
+def downgrade(engine_name):
+ globals()["downgrade_%s" % engine_name]()
+
+<%
+ from flask import current_app
+ bind_names = []
+ if current_app.config.get('SQLALCHEMY_BINDS') is not None:
+ bind_names = list(current_app.config['SQLALCHEMY_BINDS'].keys())
+ else:
+ get_bind_names = getattr(current_app.extensions['migrate'].db, 'bind_names', None)
+ if get_bind_names:
+ bind_names = get_bind_names()
+ db_names = [''] + bind_names
+%>
+
+## generate an "upgrade_<xyz>() / downgrade_<xyz>()" function
+## for each database name in the ini file.
+
+% for db_name in db_names:
+
+def upgrade_${db_name}():
+ ${context.get("%s_upgrades" % db_name, "pass")}
+
+
+def downgrade_${db_name}():
+ ${context.get("%s_downgrades" % db_name, "pass")}
+
+% endfor
diff --git a/libs/flask_migrate/templates/aioflask/README b/libs/flask_migrate/templates/aioflask/README
new file mode 100644
index 000000000..6ed8020e0
--- /dev/null
+++ b/libs/flask_migrate/templates/aioflask/README
@@ -0,0 +1 @@
+Single-database configuration for aioflask.
diff --git a/libs/flask_migrate/templates/aioflask/alembic.ini.mako b/libs/flask_migrate/templates/aioflask/alembic.ini.mako
new file mode 100644
index 000000000..ec9d45c26
--- /dev/null
+++ b/libs/flask_migrate/templates/aioflask/alembic.ini.mako
@@ -0,0 +1,50 @@
+# A generic, single database configuration.
+
+[alembic]
+# template used to generate migration files
+# file_template = %%(rev)s_%%(slug)s
+
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
+
+# Logging configuration
+[loggers]
+keys = root,sqlalchemy,alembic,flask_migrate
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+qualname =
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+
+[logger_alembic]
+level = INFO
+handlers =
+qualname = alembic
+
+[logger_flask_migrate]
+level = INFO
+handlers =
+qualname = flask_migrate
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S
diff --git a/libs/flask_migrate/templates/aioflask/env.py b/libs/flask_migrate/templates/aioflask/env.py
new file mode 100644
index 000000000..e33496bb0
--- /dev/null
+++ b/libs/flask_migrate/templates/aioflask/env.py
@@ -0,0 +1,115 @@
+import asyncio
+import logging
+from logging.config import fileConfig
+
+from flask import current_app
+
+from alembic import context
+
+# this is the Alembic Config object, which provides
+# access to the values within the .ini file in use.
+config = context.config
+
+# Interpret the config file for Python logging.
+# This line sets up loggers basically.
+fileConfig(config.config_file_name)
+logger = logging.getLogger('alembic.env')
+
+
+def get_engine():
+ try:
+ # this works with Flask-SQLAlchemy<3 and Alchemical
+ return current_app.extensions['migrate'].db.get_engine()
+ except TypeError:
+ # this works with Flask-SQLAlchemy>=3
+ return current_app.extensions['migrate'].db.engine
+
+
+def get_engine_url():
+ try:
+ return get_engine().url.render_as_string(hide_password=False).replace(
+ '%', '%%')
+ except AttributeError:
+ return str(get_engine().url).replace('%', '%%')
+
+
+# add your model's MetaData object here
+# for 'autogenerate' support
+# from myapp import mymodel
+# target_metadata = mymodel.Base.metadata
+config.set_main_option('sqlalchemy.url', get_engine_url())
+target_db = current_app.extensions['migrate'].db
+
+# other values from the config, defined by the needs of env.py,
+# can be acquired:
+# my_important_option = config.get_main_option("my_important_option")
+# ... etc.
+
+
+def get_metadata():
+ if hasattr(target_db, 'metadatas'):
+ return target_db.metadatas[None]
+ return target_db.metadata
+
+
+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, target_metadata=get_metadata(), literal_binds=True
+ )
+
+ with context.begin_transaction():
+ context.run_migrations()
+
+
+def do_run_migrations(connection):
+ # this callback is used to prevent an auto-migration from being generated
+ # when there are no changes to the schema
+ # reference: http://alembic.zzzcomputing.com/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.')
+
+ context.configure(
+ connection=connection,
+ target_metadata=get_metadata(),
+ process_revision_directives=process_revision_directives,
+ **current_app.extensions['migrate'].configure_args
+ )
+
+ with context.begin_transaction():
+ context.run_migrations()
+
+
+async 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.
+
+ """
+
+ connectable = get_engine()
+
+ async with connectable.connect() as connection:
+ await connection.run_sync(do_run_migrations)
+
+
+if context.is_offline_mode():
+ run_migrations_offline()
+else:
+ asyncio.get_event_loop().run_until_complete(run_migrations_online())
diff --git a/libs/flask_migrate/templates/aioflask/script.py.mako b/libs/flask_migrate/templates/aioflask/script.py.mako
new file mode 100644
index 000000000..2c0156303
--- /dev/null
+++ b/libs/flask_migrate/templates/aioflask/script.py.mako
@@ -0,0 +1,24 @@
+"""${message}
+
+Revision ID: ${up_revision}
+Revises: ${down_revision | comma,n}
+Create Date: ${create_date}
+
+"""
+from alembic import op
+import sqlalchemy as sa
+${imports if imports else ""}
+
+# revision identifiers, used by Alembic.
+revision = ${repr(up_revision)}
+down_revision = ${repr(down_revision)}
+branch_labels = ${repr(branch_labels)}
+depends_on = ${repr(depends_on)}
+
+
+def upgrade():
+ ${upgrades if upgrades else "pass"}
+
+
+def downgrade():
+ ${downgrades if downgrades else "pass"}
diff --git a/libs/flask_migrate/templates/flask-multidb/README b/libs/flask_migrate/templates/flask-multidb/README
new file mode 100644
index 000000000..eaae2511a
--- /dev/null
+++ b/libs/flask_migrate/templates/flask-multidb/README
@@ -0,0 +1 @@
+Multi-database configuration for Flask.
diff --git a/libs/flask_migrate/templates/flask-multidb/alembic.ini.mako b/libs/flask_migrate/templates/flask-multidb/alembic.ini.mako
new file mode 100644
index 000000000..ec9d45c26
--- /dev/null
+++ b/libs/flask_migrate/templates/flask-multidb/alembic.ini.mako
@@ -0,0 +1,50 @@
+# A generic, single database configuration.
+
+[alembic]
+# template used to generate migration files
+# file_template = %%(rev)s_%%(slug)s
+
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
+
+# Logging configuration
+[loggers]
+keys = root,sqlalchemy,alembic,flask_migrate
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+qualname =
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+
+[logger_alembic]
+level = INFO
+handlers =
+qualname = alembic
+
+[logger_flask_migrate]
+level = INFO
+handlers =
+qualname = flask_migrate
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S
diff --git a/libs/flask_migrate/templates/flask-multidb/env.py b/libs/flask_migrate/templates/flask-multidb/env.py
new file mode 100644
index 000000000..8f3101fec
--- /dev/null
+++ b/libs/flask_migrate/templates/flask-multidb/env.py
@@ -0,0 +1,188 @@
+import logging
+from logging.config import fileConfig
+
+from sqlalchemy import MetaData
+from flask import current_app
+
+from alembic import context
+
+USE_TWOPHASE = False
+
+# this is the Alembic Config object, which provides
+# access to the values within the .ini file in use.
+config = context.config
+
+# Interpret the config file for Python logging.
+# This line sets up loggers basically.
+fileConfig(config.config_file_name)
+logger = logging.getLogger('alembic.env')
+
+
+def get_engine(bind_key=None):
+ try:
+ # this works with Flask-SQLAlchemy<3 and Alchemical
+ return current_app.extensions['migrate'].db.get_engine(bind=bind_key)
+ except TypeError:
+ # this works with Flask-SQLAlchemy>=3
+ return current_app.extensions['migrate'].db.engines.get(bind_key)
+
+
+def get_engine_url(bind_key=None):
+ try:
+ return get_engine(bind_key).url.render_as_string(
+ hide_password=False).replace('%', '%%')
+ except AttributeError:
+ return str(get_engine(bind_key).url).replace('%', '%%')
+
+
+# add your model's MetaData object here
+# for 'autogenerate' support
+# from myapp import mymodel
+# target_metadata = mymodel.Base.metadata
+config.set_main_option('sqlalchemy.url', get_engine_url())
+bind_names = []
+if current_app.config.get('SQLALCHEMY_BINDS') is not None:
+ bind_names = list(current_app.config['SQLALCHEMY_BINDS'].keys())
+else:
+ get_bind_names = getattr(current_app.extensions['migrate'].db,
+ 'bind_names', None)
+ if get_bind_names:
+ bind_names = get_bind_names()
+for bind in bind_names:
+ context.config.set_section_option(
+ bind, "sqlalchemy.url", get_engine_url(bind_key=bind))
+target_db = current_app.extensions['migrate'].db
+
+# other values from the config, defined by the needs of env.py,
+# can be acquired:
+# my_important_option = config.get_main_option("my_important_option")
+# ... etc.
+
+
+def get_metadata(bind):
+ """Return the metadata for a bind."""
+ if bind == '':
+ bind = None
+ if hasattr(target_db, 'metadatas'):
+ return target_db.metadatas[bind]
+
+ # legacy, less flexible implementation
+ m = MetaData()
+ for t in target_db.metadata.tables.values():
+ if t.info.get('bind_key') == bind:
+ t.tometadata(m)
+ return m
+
+
+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.
+
+ """
+ # for the --sql use case, run migrations for each URL into
+ # individual files.
+
+ engines = {
+ '': {
+ 'url': context.config.get_main_option('sqlalchemy.url')
+ }
+ }
+ for name in bind_names:
+ engines[name] = rec = {}
+ rec['url'] = context.config.get_section_option(name, "sqlalchemy.url")
+
+ for name, rec in engines.items():
+ logger.info("Migrating database %s" % (name or '<default>'))
+ file_ = "%s.sql" % name
+ logger.info("Writing output to %s" % file_)
+ with open(file_, 'w') as buffer:
+ context.configure(
+ url=rec['url'],
+ output_buffer=buffer,
+ target_metadata=get_metadata(name),
+ literal_binds=True,
+ )
+ with context.begin_transaction():
+ context.run_migrations(engine_name=name)
+
+
+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.
+
+ """
+
+ # this callback is used to prevent an auto-migration from being generated
+ # when there are no changes to the schema
+ # reference: http://alembic.zzzcomputing.com/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': get_engine()}
+ }
+ for name in bind_names:
+ engines[name] = rec = {}
+ rec['engine'] = get_engine(bind_key=name)
+
+ for name, rec in engines.items():
+ engine = rec['engine']
+ rec['connection'] = conn = engine.connect()
+
+ if USE_TWOPHASE:
+ rec['transaction'] = conn.begin_twophase()
+ else:
+ rec['transaction'] = conn.begin()
+
+ try:
+ for name, rec in engines.items():
+ logger.info("Migrating database %s" % (name or '<default>'))
+ context.configure(
+ connection=rec['connection'],
+ upgrade_token="%s_upgrades" % name,
+ downgrade_token="%s_downgrades" % name,
+ target_metadata=get_metadata(name),
+ process_revision_directives=process_revision_directives,
+ **current_app.extensions['migrate'].configure_args
+ )
+ context.run_migrations(engine_name=name)
+
+ if USE_TWOPHASE:
+ for rec in engines.values():
+ rec['transaction'].prepare()
+
+ for rec in engines.values():
+ rec['transaction'].commit()
+ except: # noqa: E722
+ for rec in engines.values():
+ rec['transaction'].rollback()
+ raise
+ finally:
+ for rec in engines.values():
+ rec['connection'].close()
+
+
+if context.is_offline_mode():
+ run_migrations_offline()
+else:
+ run_migrations_online()
diff --git a/libs/flask_migrate/templates/flask-multidb/script.py.mako b/libs/flask_migrate/templates/flask-multidb/script.py.mako
new file mode 100644
index 000000000..3beabc463
--- /dev/null
+++ b/libs/flask_migrate/templates/flask-multidb/script.py.mako
@@ -0,0 +1,53 @@
+<%!
+import re
+
+%>"""${message}
+
+Revision ID: ${up_revision}
+Revises: ${down_revision | comma,n}
+Create Date: ${create_date}
+
+"""
+from alembic import op
+import sqlalchemy as sa
+${imports if imports else ""}
+
+# revision identifiers, used by Alembic.
+revision = ${repr(up_revision)}
+down_revision = ${repr(down_revision)}
+branch_labels = ${repr(branch_labels)}
+depends_on = ${repr(depends_on)}
+
+
+def upgrade(engine_name):
+ globals()["upgrade_%s" % engine_name]()
+
+
+def downgrade(engine_name):
+ globals()["downgrade_%s" % engine_name]()
+
+<%
+ from flask import current_app
+ bind_names = []
+ if current_app.config.get('SQLALCHEMY_BINDS') is not None:
+ bind_names = list(current_app.config['SQLALCHEMY_BINDS'].keys())
+ else:
+ get_bind_names = getattr(current_app.extensions['migrate'].db, 'bind_names', None)
+ if get_bind_names:
+ bind_names = get_bind_names()
+ db_names = [''] + bind_names
+%>
+
+## generate an "upgrade_<xyz>() / downgrade_<xyz>()" function
+## for each database name in the ini file.
+
+% for db_name in db_names:
+
+def upgrade_${db_name}():
+ ${context.get("%s_upgrades" % db_name, "pass")}
+
+
+def downgrade_${db_name}():
+ ${context.get("%s_downgrades" % db_name, "pass")}
+
+% endfor
diff --git a/libs/flask_migrate/templates/flask/README b/libs/flask_migrate/templates/flask/README
new file mode 100644
index 000000000..0e0484415
--- /dev/null
+++ b/libs/flask_migrate/templates/flask/README
@@ -0,0 +1 @@
+Single-database configuration for Flask.
diff --git a/libs/flask_migrate/templates/flask/alembic.ini.mako b/libs/flask_migrate/templates/flask/alembic.ini.mako
new file mode 100644
index 000000000..ec9d45c26
--- /dev/null
+++ b/libs/flask_migrate/templates/flask/alembic.ini.mako
@@ -0,0 +1,50 @@
+# A generic, single database configuration.
+
+[alembic]
+# template used to generate migration files
+# file_template = %%(rev)s_%%(slug)s
+
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
+
+# Logging configuration
+[loggers]
+keys = root,sqlalchemy,alembic,flask_migrate
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+qualname =
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+
+[logger_alembic]
+level = INFO
+handlers =
+qualname = alembic
+
+[logger_flask_migrate]
+level = INFO
+handlers =
+qualname = flask_migrate
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S
diff --git a/libs/flask_migrate/templates/flask/env.py b/libs/flask_migrate/templates/flask/env.py
new file mode 100644
index 000000000..89f80b211
--- /dev/null
+++ b/libs/flask_migrate/templates/flask/env.py
@@ -0,0 +1,110 @@
+import logging
+from logging.config import fileConfig
+
+from flask import current_app
+
+from alembic import context
+
+# this is the Alembic Config object, which provides
+# access to the values within the .ini file in use.
+config = context.config
+
+# Interpret the config file for Python logging.
+# This line sets up loggers basically.
+fileConfig(config.config_file_name)
+logger = logging.getLogger('alembic.env')
+
+
+def get_engine():
+ try:
+ # this works with Flask-SQLAlchemy<3 and Alchemical
+ return current_app.extensions['migrate'].db.get_engine()
+ except TypeError:
+ # this works with Flask-SQLAlchemy>=3
+ return current_app.extensions['migrate'].db.engine
+
+
+def get_engine_url():
+ try:
+ return get_engine().url.render_as_string(hide_password=False).replace(
+ '%', '%%')
+ except AttributeError:
+ return str(get_engine().url).replace('%', '%%')
+
+
+# add your model's MetaData object here
+# for 'autogenerate' support
+# from myapp import mymodel
+# target_metadata = mymodel.Base.metadata
+config.set_main_option('sqlalchemy.url', get_engine_url())
+target_db = current_app.extensions['migrate'].db
+
+# other values from the config, defined by the needs of env.py,
+# can be acquired:
+# my_important_option = config.get_main_option("my_important_option")
+# ... etc.
+
+
+def get_metadata():
+ if hasattr(target_db, 'metadatas'):
+ return target_db.metadatas[None]
+ return target_db.metadata
+
+
+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, target_metadata=get_metadata(), literal_binds=True
+ )
+
+ 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.
+
+ """
+
+ # this callback is used to prevent an auto-migration from being generated
+ # when there are no changes to the schema
+ # reference: http://alembic.zzzcomputing.com/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.')
+
+ connectable = get_engine()
+
+ with connectable.connect() as connection:
+ context.configure(
+ connection=connection,
+ target_metadata=get_metadata(),
+ process_revision_directives=process_revision_directives,
+ **current_app.extensions['migrate'].configure_args
+ )
+
+ with context.begin_transaction():
+ context.run_migrations()
+
+
+if context.is_offline_mode():
+ run_migrations_offline()
+else:
+ run_migrations_online()
diff --git a/libs/flask_migrate/templates/flask/script.py.mako b/libs/flask_migrate/templates/flask/script.py.mako
new file mode 100644
index 000000000..2c0156303
--- /dev/null
+++ b/libs/flask_migrate/templates/flask/script.py.mako
@@ -0,0 +1,24 @@
+"""${message}
+
+Revision ID: ${up_revision}
+Revises: ${down_revision | comma,n}
+Create Date: ${create_date}
+
+"""
+from alembic import op
+import sqlalchemy as sa
+${imports if imports else ""}
+
+# revision identifiers, used by Alembic.
+revision = ${repr(up_revision)}
+down_revision = ${repr(down_revision)}
+branch_labels = ${repr(branch_labels)}
+depends_on = ${repr(depends_on)}
+
+
+def upgrade():
+ ${upgrades if upgrades else "pass"}
+
+
+def downgrade():
+ ${downgrades if downgrades else "pass"}