From: Michel Albert Date: Wed, 15 Jul 2015 05:39:24 +0000 (+0200) Subject: New CLI command: "edit" - Edits the latest rev. X-Git-Tag: rel_0_8_0~7^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7b91b325ff43a0e9235e0f15b57391fa92352626;p=thirdparty%2Fsqlalchemy%2Falembic.git New CLI command: "edit" - Edits the latest rev. Running ``alembic edit`` will open the latest revision in a text-editor. --- diff --git a/alembic/command.py b/alembic/command.py index 3ce51311..644a1691 100644 --- a/alembic/command.py +++ b/alembic/command.py @@ -353,3 +353,16 @@ def stamp(config, revision, sql=False, tag=None): tag=tag ): script.run_env() + + +def edit(config): + """Edit the latest ervision""" + + script = ScriptDirectory.from_config(config) + revisions = script.walk_revisions() + head = next(revisions) + + try: + util.open_in_editor(head.path) + except OSError as exc: + raise util.CommandError('Error executing editor (%s)' % (exc,)) diff --git a/alembic/config.py b/alembic/config.py index b3fc36fc..3d11916d 100644 --- a/alembic/config.py +++ b/alembic/config.py @@ -351,6 +351,13 @@ class CommandLine(object): action="store", help="Specify a revision range; " "format is [start]:[end]") + ), + 'edit': ( + "--edit", + dict( + action="store_true", + help="Edit the latest revision" + ) ) } positional_help = { diff --git a/alembic/util/__init__.py b/alembic/util/__init__.py index bd7196ce..0fe9f3c5 100644 --- a/alembic/util/__init__.py +++ b/alembic/util/__init__.py @@ -9,6 +9,8 @@ from .pyfiles import ( # noqa from .sqla_compat import ( # noqa sqla_07, sqla_079, sqla_08, sqla_083, sqla_084, sqla_09, sqla_092, sqla_094, sqla_094, sqla_099, sqla_100, sqla_105) +from .os_helpers import ( # noqa + open_in_editor) class CommandError(Exception): diff --git a/alembic/util/os_helpers.py b/alembic/util/os_helpers.py new file mode 100644 index 00000000..ccbfa112 --- /dev/null +++ b/alembic/util/os_helpers.py @@ -0,0 +1,49 @@ +from os.path import join, exists +from subprocess import check_call +import os + + +def open_in_editor(filename, environ=None): + ''' + Opens the given file in a text editor. If the environment vaiable ``EDITOR`` + is set, this is taken as preference. + + Otherwise, a list of commonly installed editors is tried. + + If no editor matches, an :py:exc:`OSError` is raised. + + :param filename: The filename to open. Will be passed verbatim to the + editor command. + :param environ: An optional drop-in replacement for ``os.environ``. Used + mainly for testing. + ''' + + environ = environ or os.environ + + # Look for an editor. Prefer the user's choice by env-var, fall back to most + # commonly installed editor (nano/vim) + candidates = [ + '/usr/bin/sensible-editor', + '/usr/bin/nano', + '/usr/bin/vim', + ] + + if 'EDITOR' in environ: + user_choice = environ['EDITOR'] + if '/' not in user_choice: + # Assuming this is on the PATH, we need to determine it's absolute + # location. Otherwise, ``check_call`` will fail + for path in environ.get('PATH', '').split(os.pathsep): + if exists(join(path, user_choice)): + user_choice = join(path, user_choice) + break + candidates.insert(0, user_choice) + + for path in candidates: + if exists(path): + editor = path + break + else: + raise OSError('No suitable editor found. Please set the ' + '"EDITOR" environment variable') + check_call([editor, filename]) diff --git a/tests/test_command.py b/tests/test_command.py index 00610230..ffb659b7 100644 --- a/tests/test_command.py +++ b/tests/test_command.py @@ -1,4 +1,5 @@ from alembic import command +from mock import patch from io import TextIOWrapper, BytesIO from alembic.script import ScriptDirectory from alembic.testing.fixtures import TestBase, capture_context_buffer @@ -371,3 +372,50 @@ down_revision = '%s' self.bind.scalar("select version_num from alembic_version"), self.a ) + + +class EditTest(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) + + @classmethod + def teardown_class(cls): + clear_staging_env() + + def test_edit_with_user_editor(self): + expected_call_arg = '%s/scripts/versions/%s_revision_c.py' % ( + EditTest.cfg.config_args['here'], + EditTest.c + ) + + with patch('alembic.util.os_helpers.check_call') as check_call, \ + patch('alembic.util.os_helpers.exists') as exists: + exists.side_effect = lambda fname: fname == '/usr/bin/vim' + command.edit(self.cfg) + check_call.assert_called_with(['/usr/bin/vim', expected_call_arg]) + + def test_edit_with_default_editor(self): + expected_call_arg = '%s/scripts/versions/%s_revision_c.py' % ( + EditTest.cfg.config_args['here'], + EditTest.c + ) + + with patch('alembic.util.os_helpers.check_call') as check_call, \ + patch('alembic.util.os_helpers.exists') as exists: + exists.side_effect = lambda fname: fname == '/usr/bin/vim' + command.edit(self.cfg) + check_call.assert_called_with(['/usr/bin/vim', expected_call_arg]) + + def test_edit_with_missing_editor(self): + with patch('alembic.util.os_helpers.check_call'), \ + patch('alembic.util.os_helpers.exists') as exists: + exists.return_value = False + assert_raises_message( + util.CommandError, + 'EDITOR', + command.edit, + self.cfg) diff --git a/tests/test_os_helpers.py b/tests/test_os_helpers.py new file mode 100644 index 00000000..220e1149 --- /dev/null +++ b/tests/test_os_helpers.py @@ -0,0 +1,44 @@ +from alembic import util +from alembic.testing import assert_raises_message +from alembic.testing.fixtures import TestBase + +try: + from unittest.mock import patch +except ImportError: + from mock import patch # noqa + + +class TestHelpers(TestBase): + + def test_edit_with_user_editor(self): + test_environ = { + 'EDITOR': 'myvim', + 'PATH': '/usr/bin' + } + + with patch('alembic.util.os_helpers.check_call') as check_call, \ + patch('alembic.util.os_helpers.exists') as exists: + exists.side_effect = lambda fname: fname == '/usr/bin/myvim' + util.open_in_editor('myfile', test_environ) + check_call.assert_called_with(['/usr/bin/myvim', 'myfile']) + + def test_edit_with_default_editor(self): + test_environ = {} + + with patch('alembic.util.os_helpers.check_call') as check_call, \ + patch('alembic.util.os_helpers.exists') as exists: + exists.side_effect = lambda fname: fname == '/usr/bin/vim' + util.open_in_editor('myfile', test_environ) + check_call.assert_called_with(['/usr/bin/vim', 'myfile']) + + def test_edit_with_missing_editor(self): + test_environ = {} + with patch('alembic.util.os_helpers.check_call'), \ + patch('alembic.util.os_helpers.exists') as exists: + exists.return_value = False + assert_raises_message( + OSError, + 'EDITOR', + util.open_in_editor, + 'myfile', + test_environ)