Repository: timothycrosley/isort
Pull request #1026 (2019-10-07 13:38:19)

Fix bad sort with force_single_line and sort_within_sections
With both force_single_line and sort_within_sections enabled, modules
are not reliably sorted alphabetically; instead, all imports that
would be combined if force_single_line were false are instead grouped
together, even if another import should (lexicographically) be
somewhere in their midst. The new test case provides a concrete
example.

Also makes a small regex performance enhancement by precompiling
regexes used when force_single_line=True


Patch:
diff --git a/isort/isort.py b/isort/isort.py
index 48f65453..62ba7bfc 100644
--- a/isort/isort.py
+++ b/isort/isort.py
@@ -66,6 +66,9 @@ if TYPE_CHECKING:
 
 
 class _SortImports:
+    _import_line_intro_re = re.compile("^(?:from|import) ")
+    _import_line_midline_import_re = re.compile(" import ")
+
     def __init__(
         self, file_contents: str, config: Dict[str, Any], extension: str = "py"
     ) -> None:
@@ -426,7 +429,7 @@ class _SortImports:
                     )
                     from_imports = None
                 elif self.config["force_single_line"]:
-                    import_statements = []
+                    import_statement = None
                     while from_imports:
                         from_import = from_imports.pop(0)
                         single_import_line = self._add_comments(
@@ -447,20 +450,19 @@ class _SortImports:
                                 self.config["keep_direct_and_as_imports"]
                                 and self.imports[section]["from"][module][from_import]
                             ):
-                                import_statements.append(self._wrap(single_import_line))
+                                section_output.append(self._wrap(single_import_line))
                             from_comments = self.comments["straight"].get(
                                 "{}.{}".format(module, from_import)
                             )
-                            import_statements.extend(
+                            section_output.extend(
                                 self._add_comments(
                                     from_comments, self._wrap(import_start + as_import)
                                 )
                                 for as_import in nsorted(as_imports[from_import])
                             )
                         else:
-                            import_statements.append(self._wrap(single_import_line))
+                            section_output.append(self._wrap(single_import_line))
                         comments = None
-                    import_statement = self.line_separator.join(import_statements)
                 else:
                     while from_imports and from_imports[0] in as_imports:
                         from_import = from_imports.pop(0)
@@ -730,8 +732,9 @@ class _SortImports:
                     if line.startswith("#"):
                         return "AA"
 
-                    line = re.sub("^from ", "", line)
-                    line = re.sub("^import ", "", line)
+                    line = self._import_line_intro_re.sub(
+                        "", self._import_line_midline_import_re.sub(".", line)
+                    )
                     if line.split(" ")[0] in self.config["force_to_top"]:
                         section = "A"
                     if not self.config["order_by_type"]:


Test patch:
diff --git a/test_isort.py b/test_isort.py
index ae5eee89..f19b96ad 100644
--- a/test_isort.py
+++ b/test_isort.py
@@ -1066,6 +1066,26 @@ def test_force_single_line_long_imports() -> None:
     )
 
 
+def test_force_single_line_imports_and_sort_within_sections() -> None:
+    test_input = (
+        "from third_party import lib_a, lib_b, lib_d\n"
+        "from third_party.lib_c import lib1\n"
+    )
+    test_output = SortImports(
+        file_contents=test_input,
+        multi_line_output=WrapModes.GRID,
+        line_length=40,
+        force_single_line=True,
+        force_sort_within_sections=True,
+    ).output
+    assert test_output == (
+        "from third_party import lib_a\n"
+        "from third_party import lib_b\n"
+        "from third_party.lib_c import lib1\n"
+        "from third_party import lib_d\n"
+    )
+
+
 def test_titled_imports() -> None:
     """Tests setting custom titled/commented import sections."""
     test_input = (