]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
New CLI command: "edit" - Edits the latest rev.
authorMichel Albert <michel@albert.lu>
Wed, 15 Jul 2015 05:39:24 +0000 (07:39 +0200)
committerMichel Albert <michel@albert.lu>
Wed, 15 Jul 2015 15:55:40 +0000 (17:55 +0200)
Running ``alembic edit`` will open the latest revision in a text-editor.

alembic/command.py
alembic/config.py
alembic/util/__init__.py
alembic/util/os_helpers.py [new file with mode: 0644]
tests/test_command.py
tests/test_os_helpers.py [new file with mode: 0644]

index 3ce5131100cdba4e3846f9ca6a3e611195bfa56d..644a1691a8c894daf57c978097d5e8e0102f54e0 100644 (file)
@@ -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,))
index b3fc36fc71b23e1977f02698e92687f942812b4d..3d11916d17a5524fee37137d26dc6c7ee5adb174 100644 (file)
@@ -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 = {
index bd7196ce4749dc3e4930d8687b5f4aad1deb270b..0fe9f3c53e26c8028f518cbded9da57d96d88405 100644 (file)
@@ -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 (file)
index 0000000..ccbfa11
--- /dev/null
@@ -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])
index 00610230bf6861521273fb04b395a594a3034858..ffb659b799e244fbae70f7e983d17ca1b4969341 100644 (file)
@@ -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 (file)
index 0000000..220e114
--- /dev/null
@@ -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)