self.metadata = metadata = opts.get('target_metadata', None) \
if metadata is None else metadata
- if metadata is None and \
+ if autogenerate and metadata is None and \
migration_context is not None and \
migration_context.script is not None:
raise util.CommandError(
"""Maintains configuration and state that's specific to a revision
file generation operation."""
- def __init__(self, config, script_directory, command_args):
+ def __init__(self, config, script_directory, command_args,
+ process_revision_directives=None):
self.config = config
self.script_directory = script_directory
self.command_args = command_args
+ self.process_revision_directives = process_revision_directives
self.template_args = {
'config': config # Let templates use config for
# e.g. multiple databases
compare._populate_migration_script(
autogen_context, migration_script)
+ if self.process_revision_directives:
+ self.process_revision_directives(
+ migration_context, rev, self.generated_revisions)
+
hook = migration_context.opts['process_revision_directives']
if hook:
hook(migration_context, rev, self.generated_revisions)
def revision(
config, message=None, autogenerate=False, sql=False,
head="head", splice=False, branch_label=None,
- version_path=None, rev_id=None, depends_on=None):
+ version_path=None, rev_id=None, depends_on=None,
+ process_revision_directives=None):
"""Create a new revision file."""
script_directory = ScriptDirectory.from_config(config)
version_path=version_path, rev_id=rev_id, depends_on=depends_on
)
revision_context = autogen.RevisionContext(
- config, script_directory, command_args)
+ config, script_directory, command_args,
+ process_revision_directives=process_revision_directives)
environment = util.asbool(
config.get_main_option("revision_environment")
try:
fn(config,
- *[getattr(options, k) for k in positional],
- **dict((k, getattr(options, k)) for k in kwarg)
+ *[getattr(options, k, None) for k in positional],
+ **dict((k, getattr(options, k, None)) for k in kwarg)
)
except util.CommandError as e:
if options.raiseerr:
for the inner type as well as the outer type. Pull request courtesy
Paul Brackin.
+ .. change:: process_revision_directives_command
+ :tags: feature, autogenerate
+
+ Added a keyword argument ``process_revision_directives`` to the
+ :func:`.command.revision` API call. This function acts in the
+ same role as the environment-level call, and allows API use of the
+ command to drop in an ad-hoc directive process function. This
+ function can be used among other things to place a complete
+ :class:`.MigrationScript` structure in place.
+
.. change:: fk_schema_compare
:tags: bug, operations
from alembic import command
from io import TextIOWrapper, BytesIO
from alembic.script import ScriptDirectory
+from alembic import config
from alembic.testing.fixtures import TestBase, capture_context_buffer
from alembic.testing.env import staging_env, _sqlite_testing_config, \
three_rev_fixture, clear_staging_env, _no_sql_testing_config, \
with mock.patch('alembic.util.edit') as edit:
command.edit(self.cfg, "current")
edit.assert_called_with(expected_call_arg)
+
+
+class CommandLineTest(TestBase):
+ @classmethod
+ def setup_class(cls):
+ cls.env = staging_env()
+ cls.cfg = _sqlite_testing_config()
+ cls.a, cls.b, cls.c = three_rev_fixture(cls.cfg)
+
+ def test_run_cmd_args_missing(self):
+ canary = mock.Mock()
+
+ orig_revision = command.revision
+
+ # the command function has "process_revision_directives"
+ # however the ArgumentParser does not. ensure things work
+ def revision(
+ config, message=None, autogenerate=False, sql=False,
+ head="head", splice=False, branch_label=None,
+ version_path=None, rev_id=None, depends_on=None,
+ process_revision_directives=None
+ ):
+ canary(
+ config, message=message
+ )
+
+ revision.__module__ = 'alembic.command'
+
+ # CommandLine() pulls the function into the ArgumentParser
+ # and needs the full signature, so we can't patch the "revision"
+ # command normally as ArgumentParser gives us no way to get to it.
+ config.command.revision = revision
+ try:
+ commandline = config.CommandLine()
+ options = commandline.parser.parse_args(["revision", "-m", "foo"])
+ commandline.run_cmd(self.cfg, options)
+ finally:
+ config.command.revision = orig_revision
+ eq_(
+ canary.mock_calls,
+ [mock.call(self.cfg, message="foo")]
+ )
)
-
-
class CustomizeRevisionTest(TestBase):
def setUp(self):
self.env = staging_env()
["alembic_version"]
)
+ def test_programmatic_command_option(self):
+
+ def process_revision_directives(context, rev, generate_revisions):
+ generate_revisions[0].message = "test programatic"
+ generate_revisions[0].upgrade_ops = ops.UpgradeOps(
+ ops=[
+ ops.CreateTableOp(
+ 'test_table',
+ [
+ sa.Column('id', sa.Integer(), primary_key=True),
+ sa.Column('name', sa.String(50), nullable=False)
+ ]
+ ),
+ ]
+ )
+ generate_revisions[0].downgrade_ops = ops.DowngradeOps(
+ ops=[
+ ops.DropTableOp('test_table')
+ ]
+ )
+
+ with self._env_fixture(None, None):
+ rev = command.revision(
+ self.cfg,
+ head="model1@head",
+ process_revision_directives=process_revision_directives)
+
+ result = open(rev.path).read()
+ assert ("""
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table('test_table',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('name', sa.String(length=50), nullable=False),
+ sa.PrimaryKeyConstraint('id')
+ )
+ # ### end Alembic commands ###
+""") in result
+
class ScriptAccessorTest(TestBase):
def test_upgrade_downgrade_ops_list_accessors(self):
eq_(
[rev.revision for rev in script.walk_revisions()],
[self.model1, self.model2, self.model3]
- )
\ No newline at end of file
+ )