def iterate_revisions(self, upper, lower): """Iterate through script revisions, starting at the given upper revision identifier and ending at the lower. The traversal uses strictly the `down_revision` marker inside each migration script, so it is a requirement that upper >= lower, else you'll get nothing back. The iterator yields :class:`.Script` objects. """ if upper is not None and _relative_destination.match(upper): relative = int(upper) revs = list(self._iterate_revisions("head", lower)) revs = revs[-relative:] if len(revs) != abs(relative): raise util.CommandError("Relative revision %s didn't " "produce %d migrations" % (upper, abs(relative))) return iter(revs) elif lower is not None and _relative_destination.match(lower): relative = int(lower) revs = list(self._iterate_revisions(upper, "base")) revs = revs[0:-relative] if len(revs) != abs(relative): raise util.CommandError("Relative revision %s didn't " "produce %d migrations" % (lower, abs(relative))) return iter(revs) else: return self._iterate_revisions(upper, lower)
def downgrade(config, revision, sql=False, tag=None): """Revert to a previous version.""" script = ScriptDirectory.from_config(config) starting_rev = None if ":" in revision: if not sql: raise util.CommandError("Range revision not allowed") starting_rev, revision = revision.split(':', 2) elif sql: raise util.CommandError("downgrade with --sql requires <fromrev>:<torev>") def downgrade(rev, context): return script._downgrade_revs(revision, rev) with EnvironmentContext( config, script, fn=downgrade, as_sql=sql, starting_rev=starting_rev, destination_rev=revision, tag=tag ): script.run_env()
def init(): """ Prepare directory with alembic.ini, mako-files and directory for migrations. Part of functional was copied from original Alembic Init but changed some things with config """ if os.access(config.alembic_dir, os.F_OK): raise util.CommandError("Directory {} already exists".format( config.alembic_dir)) template_dir = os.path.join(config.template_path + config.template_name) if not os.access(template_dir, os.F_OK): raise util.CommandError("No such template {}".format(template_dir)) util.status( "Creating directory {}".format(os.path.abspath( config.alembic_dir)), os.makedirs, config.alembic_dir) versions = os.path.join(config.alembic_dir, 'versions') util.status("Creating directory %s" % os.path.abspath(versions), os.makedirs, versions) script = ScriptDirectory(config.alembic_dir) dirs = os.listdir(template_dir) dirs += [ 'versions/create_table_alembic_version_history.py', ] for file_ in dirs: file_path = os.path.join(template_dir, file_) if file_ == 'alembic.ini.mako': config_file = os.path.abspath('alembic.ini') if os.access(config_file, os.F_OK): util.msg( "File {} already exists, skipping".format(config_file)) else: script._generate_template( template_dir + '/alembic.ini.mako', os.path.join(config.alembic_dir, 'alembic.ini'), script_location=config.alembic_dir) elif os.path.isfile(file_path): output_file = os.path.join(config.alembic_dir, file_) script._copy_file(file_path, output_file) util.msg("Please edit configuration/connection/logging " "settings in {} before proceeding.".format( os.path.join(config.alembic_dir, 'alembic.ini')))
def _produce_migration_diffs(context, template_args, imports, include_symbol=None, include_schemas=False): opts = context.opts metadata = opts['target_metadata'] include_symbol = opts.get('include_symbol', include_symbol) include_schemas = opts.get('include_schemas', include_schemas) if metadata is None: raise util.CommandError( "Can't proceed with --autogenerate option; environment " "script %s does not provide " "a MetaData object to the context." % (context.script.env_py_location)) autogen_context, connection = _autogen_context(context, imports) diffs = [] _produce_net_changes(connection, metadata, diffs, autogen_context, include_symbol, include_schemas) template_args[opts['upgrade_token']] = \ _indent(_produce_upgrade_commands(diffs, autogen_context)) template_args[opts['downgrade_token']] = \ _indent(_produce_downgrade_commands(diffs, autogen_context)) template_args['imports'] = "\n".join(sorted(imports))
def retrieve_migrations(rev, context): if set(script_directory.get_revisions(rev)) != \ set(script_directory.get_revisions("heads")): raise util.CommandError("Target database is not up to date.") imports = set() autogen._produce_migration_diffs(context, template_args, imports) return []
def downgrade(self, revision, sql=False, tag=None): """Revert to a previous version. :param revision: string revision target or range for --sql mode :param sql: if True, use ``--sql`` mode :param tag: an arbitrary "tag" that can be intercepted by custom ``env.py`` scripts via the :meth:`.EnvironmentContext.get_tag_argument` method. """ config = self.config script = self.script_directory config.attributes["engine"] = self.engine output_buffer = io.StringIO() config.attributes["output_buffer"] = output_buffer starting_rev = None if ":" in revision: if not sql: raise util.CommandError("Range revision not allowed") starting_rev, revision = revision.split(":", 2) elif sql: raise util.CommandError( "downgrade with --sql requires <fromrev>:<torev>" ) def do_downgrade(rev, context): return script._downgrade_revs(revision, rev) with EnvironmentContext( config, script, fn=do_downgrade, as_sql=sql, starting_rev=starting_rev, destination_rev=revision, tag=tag, ): script.run_env() output_buffer.seek(0) return output_buffer.read()
def __init__(self, dir, file_template=_default_file_template): self.dir = dir self.versions = os.path.join(self.dir, 'versions') self.file_template = file_template if not os.access(dir, os.F_OK): raise util.CommandError("Path doesn't exist: %r. Please use " "the 'init' command to create a new " "scripts folder." % dir)
def alter_column(self, table_name, column_name, nullable=None, server_default=False, name=None, type_=None, schema=None, autoincrement=None, existing_type=None, existing_server_default=None, existing_nullable=None, existing_autoincrement=None): if nullable is not None and existing_type is None: if type_ is not None: existing_type = type_ # the NULL/NOT NULL alter will handle # the type alteration type_ = None else: raise util.CommandError( "MS-SQL ALTER COLUMN operations " "with NULL or NOT NULL require the " "existing_type or a new type_ be passed.") super(MSSQLImpl, self).alter_column(table_name, column_name, nullable=nullable, type_=type_, schema=schema, autoincrement=autoincrement, existing_type=existing_type, existing_nullable=existing_nullable, existing_autoincrement=existing_autoincrement) if server_default is not False: if existing_server_default is not False or \ server_default is None: self._exec( _exec_drop_col_constraint(self, table_name, column_name, 'sys.default_constraints')) if server_default is not None: super(MSSQLImpl, self).alter_column(table_name, column_name, schema=schema, server_default=server_default) if name is not None: super(MSSQLImpl, self).alter_column(table_name, column_name, schema=schema, name=name)
def history(self, rev_range="base:heads", indicate_current=False): """List changeset scripts in chronological order. :param rev_range: string revision range :param indicate_current: indicate current revision. ..versionadded:: 0.9.9 """ if rev_range is not None: if ":" not in rev_range: raise util.CommandError( "History range requires [start]:[end], " "[start]:, or :[end]" ) base, head = rev_range.strip().split(":") else: base = head = None environment = ( util.asbool(self.config.get_main_option("revision_environment")) or indicate_current ) def _display_history(base, head, currents=()): history = list() for sc in self.script_directory.walk_revisions( base=base or "base", head=head or "heads" ): if indicate_current: sc._db_current_indicator = sc in currents history.insert(0, sc) return history def _display_history_w_current(base, head): def _display_current_history(rev): if head == "current": return _display_history(base, rev, rev) elif base == "current": return _display_history(rev, head, rev) else: return _display_history(base, head, rev) return [] rev = self.current return _display_current_history(rev) if base == "current" or head == "current" or environment: return _display_history_w_current(base, head) else: return _display_history(base, head)
def init(config, directory, template='generic'): """Initialize a new scripts directory.""" if os.access(directory, os.F_OK): raise util.CommandError("Directory %s already exists" % directory) template_dir = os.path.join(config.get_template_directory(), template) if not os.access(template_dir, os.F_OK): raise util.CommandError("No such template %r" % template) util.status("Creating directory %s" % os.path.abspath(directory), os.makedirs, directory) versions = os.path.join(directory, 'versions') util.status("Creating directory %s" % os.path.abspath(versions), os.makedirs, versions) script = ScriptDirectory(directory) for file_ in os.listdir(template_dir): file_path = os.path.join(template_dir, file_) if file_ == 'alembic.ini.mako': config_file = os.path.abspath(config.config_file_name) if os.access(config_file, os.F_OK): util.msg("File %s already exists, skipping" % config_file) else: script._generate_template( file_path, config_file, script_location=directory ) elif os.path.isfile(file_path): output_file = os.path.join(directory, file_) script._copy_file( file_path, output_file ) util.msg("Please edit configuration/connection/logging "\ "settings in %r before proceeding." % config_file)
def get_revision(self, id_): """Return the :class:`.Script` instance with the given rev id.""" id_ = self.as_revision_number(id_) try: return self._revision_map[id_] except KeyError: # do a partial lookup revs = [ x for x in self._revision_map if x is not None and x.startswith(id_) ] if not revs: raise util.CommandError("No such revision '%s'" % id_) elif len(revs) > 1: raise util.CommandError("Multiple revisions start " "with '%s', %s..." % (id_, ", ".join("'%s'" % r for r in revs[0:3]))) else: return self._revision_map[revs[0]]
def get_section_option(self, section, name, default=None): """Return an option from the given section of the .ini file. """ if not self.file_config.has_section(section): raise util.CommandError("No config file %r found, or file has no " "'[%s]' section" % (self.config_file_name, section)) if self.file_config.has_option(section, name): return self.file_config.get(section, name) else: return default
def _iterate_revisions(self, upper, lower): lower = self.get_revision(lower) upper = self.get_revision(upper) orig = lower.revision if lower else 'base', \ upper.revision if upper else 'base' script = upper while script != lower: if script is None and lower is not None: raise util.CommandError( "Revision %s is not an ancestor of %s" % orig) yield script downrev = script.down_revision script = self._revision_map[downrev]
def get_revisions(config, rev_range=None): script = ScriptDirectory.from_config(config) if rev_range is not None: if ":" not in rev_range: raise util.CommandError( "History range requires [start]:[end], [start]:, or :[end]") base, head = rev_range.strip().split(":") else: base = head = None return script.walk_revisions( base=base or "base", head=head or "heads", )
def from_config(cls, config): """Produce a new :class:`.ScriptDirectory` given a :class:`.Config` instance. The :class:`.Config` need only have the ``script_location`` key present. """ script_location = config.get_main_option('script_location') if script_location is None: raise util.CommandError("No 'script_location' key " "found in configuration.") return ScriptDirectory( util.coerce_resource_to_filename(script_location), file_template=config.get_main_option('file_template', _default_file_template))
def get_current_head(self): """Return the current head revision. If the script directory has multiple heads due to branching, an error is raised. Returns a string revision number. """ current_heads = self.get_heads() if len(current_heads) > 1: raise util.CommandError("Only a single head supported so far...") if current_heads: return current_heads[0] else: return None
def get_current_revision(self): """Return the current revision, usually that which is present in the ``alembic_version`` table in the database. If this :class:`.MigrationContext` was configured in "offline" mode, that is with ``as_sql=True``, the ``starting_rev`` parameter is returned instead, if any. """ if self.as_sql: return self._start_from_rev else: if self._start_from_rev: raise util.CommandError("Can't specify current_rev to context " "when using a database connection") self._version.create(self.connection, checkfirst=True) return self.connection.scalar(self._version.select())
def __init__(self, name, column_name, schema=None, newname=None, type_=None, nullable=None, default=False, autoincrement=None): super(AlterColumn, self).__init__(name, schema=schema) self.column_name = column_name self.nullable = nullable self.newname = newname self.default = default self.autoincrement = autoincrement if type_ is None: raise util.CommandError( "All MySQL ALTER COLUMN operations " "require the existing type." ) self.type_ = sqltypes.to_instance(type_)
def mkdir(self): """Create the script directory and template.""" script_dir = self.config.get_main_option("script_location") template_src = os.path.join(self.config.get_template_directory(), "generic", "script.py.mako") template_dest = os.path.join(script_dir, "script.py.mako") if not os.access(template_src, os.F_OK): raise util.CommandError( "Template {0} does not exist".format(template_src)) if not os.access(script_dir, os.F_OK): os.makedirs(script_dir) if not os.access(template_dest, os.F_OK): shutil.copy(template_src, template_dest) for version_location in self.script_directory._version_locations: if not os.access(version_location, os.F_OK): os.makedirs(version_location)
def _from_filename(cls, dir_, filename): if not _rev_file.match(filename): return None module = util.load_python_file(dir_, filename) if not hasattr(module, "revision"): # attempt to get the revision id from the script name, # this for legacy only m = _legacy_rev.match(filename) if not m: raise util.CommandError( "Could not determine revision id from filename %s. " "Be sure the 'revision' variable is " "declared inside the script (please see 'Upgrading " "from Alembic 0.1 to 0.2' in the documentation)." % filename) else: revision = m.group(1) else: revision = module.revision return Script(module, revision, os.path.join(dir_, filename))
def get_starting_revision_argument(self): """Return the 'starting revision' argument, if the revision was passed using ``start:end``. This is only meaningful in "offline" mode. Returns ``None`` if no value is available or was configured. This function does not require that the :class:`.MigrationContext` has been configured. """ if self._migration_context is not None: return self.script._as_rev_number( self.get_context()._start_from_rev) elif 'starting_rev' in self.context_opts: return self.script._as_rev_number( self.context_opts['starting_rev']) else: raise util.CommandError( "No starting revision argument is available.")
def upgrade(config, revision, sql=False, tag=None): """Upgrade to a later version.""" script = ScriptDirectory.from_config(config) starting_rev = None if ":" in revision: if not sql: raise util.CommandError("Range revision not allowed") starting_rev, revision = revision.split(':', 2) def upgrade(rev, context): return script._upgrade_revs(revision, rev) with EnvironmentContext( config, script, fn = upgrade, as_sql = sql, starting_rev = starting_rev, destination_rev = revision, tag = tag ): script.run_env()
def init(self, directory, template="mampoer_generic", package=False): """Initialize a new scripts directory. :param template: string name of the migration environment template to use. :param package: when True, write ``__init__.py`` files into the environment location as well as the versions/ location. .. versionadded:: 1.2 """ if os.access(directory, os.F_OK) and os.listdir(directory): raise util.CommandError( "Directory %s already exists and is not empty" % directory) template_dir = os.path.join(self.get_template_directory(), template) if not os.access(template_dir, os.F_OK): raise util.CommandError("No such template %r" % template) if not os.access(directory, os.F_OK): util.status( "Creating directory %s" % os.path.abspath(directory), os.makedirs, directory, ) versions = os.path.join(directory, "versions") util.status( "Creating directory %s" % os.path.abspath(versions), os.makedirs, versions, ) script = ScriptDirectory(directory) for file_ in os.listdir(template_dir): file_path = os.path.join(template_dir, file_) if file_ == "mampoer.ini.mako": config_file = os.path.abspath(self.config.config_file_name) if os.access(config_file, os.F_OK): util.msg("File %s already exists, skipping" % config_file) else: script._generate_template(file_path, config_file, script_location=directory) elif os.path.isfile(file_path): output_file = os.path.join(directory, file_) script._copy_file(file_path, output_file) if package: for path in [ os.path.join(os.path.abspath(directory), "__init__.py"), os.path.join(os.path.abspath(versions), "__init__.py"), ]: file_ = util.status("Adding %s" % path, open, path, "w") file_.close() util.msg("Please edit configuration/connection/logging " "settings in %r before proceeding." % config_file)
def stamp(self, revision, sql=False, tag=None, purge=False): """'stamp' the revision table with the given revision; don't run any migrations. :param revision: target revision or list of revisions. May be a list to indicate stamping of multiple branch heads. .. note:: this parameter is called "revisions" in the command line interface. .. versionchanged:: 1.2 The revision may be a single revision or list of revisions when stamping multiple branch heads. :param sql: use ``--sql`` mode :param tag: an arbitrary "tag" that can be intercepted by custom ``env.py`` scripts via the :class:`.EnvironmentContext.get_tag_argument` method. :param purge: delete all entries in the version table before stamping. .. versionadded:: 1.2 """ config = self.config script = self.script_directory config.attributes["engine"] = self.engine output_buffer = io.StringIO() config.attributes["output_buffer"] = output_buffer starting_rev = None if sql: destination_revs = [] for _revision in util.to_list(revision): if ":" in _revision: srev, _revision = _revision.split(":", 2) if starting_rev != srev: if starting_rev is None: starting_rev = srev else: raise util.CommandError( "Stamp operation with --sql only supports a " "single starting revision at a time") destination_revs.append(_revision) else: destination_revs = util.to_list(revision) def do_stamp(rev, context): return script._stamp_revs(util.to_tuple(destination_revs), rev) with EnvironmentContext( config, script, fn=do_stamp, as_sql=sql, starting_rev=starting_rev if sql else None, destination_rev=util.to_tuple(destination_revs), tag=tag, purge=purge, ): script.run_env() output_buffer.seek(0) return output_buffer.read()
def retrieve_migrations(rev, context): if script.get_revision(rev) is not script.get_revision("head"): raise util.CommandError("Target database is not up to date.") autogen._produce_migration_diffs(context, template_args, imports) return []