]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
Support version_locations paths with spaces
authorGord Thompson <gord@gordthompson.com>
Sat, 12 Jun 2021 21:12:17 +0000 (15:12 -0600)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 22 Jun 2021 18:00:26 +0000 (14:00 -0400)
Fixes: #842
Change-Id: Icae7899cecc137eaba26cd14ded9da89df35aae1

alembic/script/base.py
alembic/templates/async/alembic.ini.mako
alembic/templates/generic/alembic.ini.mako
alembic/templates/multidb/alembic.ini.mako
alembic/templates/pylons/alembic.ini.mako
docs/build/branches.rst
docs/build/tutorial.rst
docs/build/unreleased/842.rst [new file with mode: 0644]
tests/test_config.py

index 723c01161445dde51203b313239533aaf3f82fdb..f6c5070f776bd2fbbbc202f6f0094e709b30c436 100644 (file)
@@ -140,7 +140,40 @@ class ScriptDirectory(object):
 
         version_locations = config.get_main_option("version_locations")
         if version_locations:
-            version_locations = _split_on_space_comma.split(version_locations)
+            version_path_separator = config.get_main_option(
+                "version_path_separator"
+            )
+
+            split_on_path = {
+                None: None,
+                "space": " ",
+                "os": os.pathsep,
+                ":": ":",
+                ";": ";",
+            }
+
+            try:
+                split_char = split_on_path[version_path_separator]
+            except KeyError as ke:
+                util.raise_(
+                    ValueError(
+                        "'%s' is not a valid value for "
+                        "version_path_separator; "
+                        "expected 'space', 'os', ':', ';'"
+                        % version_path_separator
+                    ),
+                    from_=ke,
+                )
+            else:
+                if split_char is None:
+                    # legacy behaviour for backwards compatibility
+                    version_locations = _split_on_space_comma.split(
+                        version_locations
+                    )
+                else:
+                    version_locations = [
+                        x for x in version_locations.split(split_char) if x
+                    ]
 
         prepend_sys_path = config.get_main_option("prepend_sys_path")
         if prepend_sys_path:
index 64af30188c4d3ac859cfc2ccd2cb808d0b62beaf..fa44a8f8a5a609f8f01c3aaf08939a542ad41a1d 100644 (file)
@@ -32,10 +32,19 @@ prepend_sys_path = .
 # versions/ directory
 # sourceless = false
 
-# version location specification; this defaults
+# version location specification; This defaults
 # to ${script_location}/versions.  When using multiple version
-# directories, initial revisions must be specified with --version-path
-# version_locations = %(here)s/bar %(here)s/bat ${script_location}/versions
+# directories, initial revisions must be specified with --version-path.
+# The path separator used here should be the separator specified by "version_path_separator"
+# version_locations = %(here)s/bar:%(here)s/bat:${script_location}/versions
+
+# version path separator; As mentioned above, this is the character used to split
+# version_locations. Valid values are:
+#
+# version_path_separator = :
+# version_path_separator = ;
+# version_path_separator = space
+version_path_separator = os  # default: use os.pathsep
 
 # the output encoding used when revision files
 # are written from script.py.mako
index 64af30188c4d3ac859cfc2ccd2cb808d0b62beaf..fa44a8f8a5a609f8f01c3aaf08939a542ad41a1d 100644 (file)
@@ -32,10 +32,19 @@ prepend_sys_path = .
 # versions/ directory
 # sourceless = false
 
-# version location specification; this defaults
+# version location specification; This defaults
 # to ${script_location}/versions.  When using multiple version
-# directories, initial revisions must be specified with --version-path
-# version_locations = %(here)s/bar %(here)s/bat ${script_location}/versions
+# directories, initial revisions must be specified with --version-path.
+# The path separator used here should be the separator specified by "version_path_separator"
+# version_locations = %(here)s/bar:%(here)s/bat:${script_location}/versions
+
+# version path separator; As mentioned above, this is the character used to split
+# version_locations. Valid values are:
+#
+# version_path_separator = :
+# version_path_separator = ;
+# version_path_separator = space
+version_path_separator = os  # default: use os.pathsep
 
 # the output encoding used when revision files
 # are written from script.py.mako
index c3b0ec0fa61e84fb810f0af9176ce6537320faed..1f76ab563d2f3565b99a6b7bbe6cc5463e371d93 100644 (file)
@@ -32,10 +32,19 @@ prepend_sys_path = .
 # versions/ directory
 # sourceless = false
 
-# version location specification; this defaults
+# version location specification; This defaults
 # to ${script_location}/versions.  When using multiple version
-# directories, initial revisions must be specified with --version-path
-# version_locations = %(here)s/bar %(here)s/bat ${script_location}/versions
+# directories, initial revisions must be specified with --version-path.
+# The path separator used here should be the separator specified by "version_path_separator"
+# version_locations = %(here)s/bar:%(here)s/bat:${script_location}/versions
+
+# version path separator; As mentioned above, this is the character used to split
+# version_locations. Valid values are:
+#
+# version_path_separator = :
+# version_path_separator = ;
+# version_path_separator = space
+version_path_separator = os  # default: use os.pathsep
 
 # the output encoding used when revision files
 # are written from script.py.mako
index aa745a8ad55385552913adf99d70cee5c3b69079..c7313547813059304ba94e050dd2b097fa6f5146 100644 (file)
@@ -32,10 +32,19 @@ prepend_sys_path = .
 # versions/ directory
 # sourceless = false
 
-# version location specification; this defaults
+# version location specification; This defaults
 # to ${script_location}/versions.  When using multiple version
-# directories, initial revisions must be specified with --version-path
-# version_locations = %(here)s/bar %(here)s/bat ${script_location}/versions
+# directories, initial revisions must be specified with --version-path.
+# The path separator used here should be the separator specified by "version_path_separator"
+# version_locations = %(here)s/bar:%(here)s/bat:${script_location}/versions
+
+# version path separator; As mentioned above, this is the character used to split
+# version_locations. Valid values are:
+#
+# version_path_separator = :
+# version_path_separator = ;
+# version_path_separator = space
+version_path_separator = os  # default: use os.pathsep
 
 # the output encoding used when revision files
 # are written from script.py.mako
index 196407845358a0d97e5f6efd7c82f6c2fe4c87aa..f3e5f4b921562a32e1004f358b9f060623efc562 100644 (file)
@@ -524,6 +524,7 @@ directory as one of them::
   # version location specification; this defaults
   # to foo/versions.  When using multiple version
   # directories, initial revisions must be specified with --version-path
+  version_path_separator = space
   version_locations = %(here)s/model/networking %(here)s/alembic/versions
 
 The new directory ``%(here)s/model/networking`` is in terms of where
index f575fc7b0c5090ac2516e5614cd61f9fc8f9cffd..c07a3b9644fba0d25eebb98fe39aa5f8a9b233c1 100644 (file)
@@ -159,10 +159,19 @@ The file generated with the "generic" configuration looks like::
     # versions/ directory
     # sourceless = false
 
-    # version location specification; this defaults
-    # to alembic/versions.  When using multiple version
-    # directories, initial revisions must be specified with --version-path
-    # version_locations = %(here)s/bar %(here)s/bat alembic/versions
+    # version location specification; This defaults
+    # to ${script_location}/versions.  When using multiple version
+    # directories, initial revisions must be specified with --version-path.
+    # The path separator used here should be the separator specified by "version_path_separator"
+    # version_locations = %(here)s/bar:%(here)s/bat:${script_location}/versions
+
+    # version path separator; As mentioned above, this is the character used to split
+    # version_locations. Valid values are:
+    #
+    # version_path_separator = :
+    # version_path_separator = ;
+    # version_path_separator = space
+    version_path_separator = os  # default: use os.pathsep
 
     # the output encoding used when revision files
     # are written from script.py.mako
diff --git a/docs/build/unreleased/842.rst b/docs/build/unreleased/842.rst
new file mode 100644 (file)
index 0000000..15f534f
--- /dev/null
@@ -0,0 +1,9 @@
+.. change::
+    :tags: bug, environment
+    :tickets: 842
+
+    Enhance ``version_locations`` parsing to handle paths containing spaces.
+    The new configuration option ``version_path_separator`` specifies the
+    character to use when splitting the ``version_locations`` string. The
+    default for new configurations is ``version_path_separator = os``,
+    which will use ``os.pathsep`` (e.g., ``;`` on Windows).
index 3b2e957a09f0c7442ebde2c94b46f374a59fc4ec..fdb837ed6d4358bf5f4026a109dfab8f060a8a9c 100644 (file)
@@ -1,7 +1,9 @@
 #!coding: utf-8
-
+import os
+import tempfile
 
 from alembic import config
+from alembic import testing
 from alembic import util
 from alembic.migration import MigrationContext
 from alembic.operations import Operations
@@ -9,6 +11,7 @@ from alembic.script import ScriptDirectory
 from alembic.testing import assert_raises_message
 from alembic.testing import eq_
 from alembic.testing import mock
+from alembic.testing.assertions import expect_raises_message
 from alembic.testing.env import _no_sql_testing_config
 from alembic.testing.env import _write_config_file
 from alembic.testing.env import clear_staging_env
@@ -108,6 +111,91 @@ class ConfigTest(TestBase):
         cfg.attributes["connection"] = m2
         eq_(cfg.attributes, {"m1": m1, "connection": m2})
 
+    @testing.combinations(
+        (
+            "legacy raw string 1",
+            None,
+            "/foo",
+            ["/foo"],
+        ),
+        (
+            "legacy raw string 2",
+            None,
+            "/foo /bar",
+            ["/foo", "/bar"],
+        ),
+        (
+            "legacy raw string 3",
+            "space",
+            "/foo",
+            ["/foo"],
+        ),
+        (
+            "legacy raw string 4",
+            "space",
+            "/foo /bar",
+            ["/foo", "/bar"],
+        ),
+        (
+            "Linux pathsep 1",
+            ":",
+            "/Project A",
+            ["/Project A"],
+        ),
+        (
+            "Linux pathsep 2",
+            ":",
+            "/Project A:/Project B",
+            ["/Project A", "/Project B"],
+        ),
+        (
+            "Windows pathsep 1",
+            ";",
+            r"C:\Project A",
+            [r"C:\Project A"],
+        ),
+        (
+            "Windows pathsep 2",
+            ";",
+            r"C:\Project A;C:\Project B",
+            [r"C:\Project A", r"C:\Project B"],
+        ),
+        (
+            "os pathsep",
+            "os",
+            r"path_number_one%(sep)spath_number_two%(sep)s"
+            % {"sep": os.pathsep},
+            [r"path_number_one", r"path_number_two"],
+        ),
+        (
+            "invalid pathsep 2",
+            "|",
+            "/foo|/bar",
+            ValueError(
+                "'|' is not a valid value for version_path_separator; "
+                "expected 'space', 'os', ':', ';'"
+            ),
+        ),
+        id_="iaaa",
+        argnames="separator, string_value, expected_result",
+    )
+    def test_version_locations(self, separator, string_value, expected_result):
+        cfg = config.Config()
+        if separator is not None:
+            cfg.set_main_option(
+                "version_path_separator",
+                separator,
+            )
+        cfg.set_main_option("script_location", tempfile.gettempdir())
+        cfg.set_main_option("version_locations", string_value)
+
+        if isinstance(expected_result, ValueError):
+            with expect_raises_message(ValueError, expected_result.args[0]):
+                ScriptDirectory.from_config(cfg)
+        else:
+            s = ScriptDirectory.from_config(cfg)
+            eq_(s.version_locations, expected_result)
+
 
 class StdoutOutputEncodingTest(TestBase):
     def test_plain(self):