Repository: timothycrosley/isort
Pull request #1073 (2019-12-27 06:20:23)

Feature/nested import support


Patch:
diff --git a/isort/_future/__init__.py b/isort/_future/__init__.py
index 9f5e9d18..4d9ef4b7 100644
--- a/isort/_future/__init__.py
+++ b/isort/_future/__init__.py
@@ -2,6 +2,7 @@ import sys
 
 if sys.version_info.major <= 3 and sys.version_info.minor <= 6:
     from . import _dataclasses as dataclasses  # type: ignore
+
 else:
     import dataclasses  # type: ignore
 
diff --git a/isort/api.py b/isort/api.py
index 4c6bc949..c53fe98a 100644
--- a/isort/api.py
+++ b/isort/api.py
@@ -1,4 +1,5 @@
 import re
+import textwrap
 from io import StringIO
 from itertools import chain
 from pathlib import Path
@@ -154,6 +155,7 @@ def sort_imports(
     in_top_comment: bool = False
     first_import_section: bool = True
     section_comments = [f"# {heading}" for heading in config.import_headings.values()]
+    indent: str = ""
 
     for index, line in enumerate(chain(input_stream, (None,))):
         if line is None:
@@ -171,7 +173,7 @@ def sort_imports(
             stripped_line = line.strip()
             if (
                 (index == 0 or (index == 1 and not contains_imports))
-                and line.startswith("#")
+                and stripped_line.startswith("#")
                 and stripped_line not in section_comments
             ):
                 in_top_comment = True
@@ -180,7 +182,7 @@ def sort_imports(
                     in_top_comment = False
                     first_comment_index_end = index - 1
 
-            if not line.startswith("#") and '"' in line or "'" in line:
+            if not stripped_line.startswith("#") and '"' in line or "'" in line:
                 char_index = 0
                 if first_comment_index_start == -1 and (
                     line.startswith('"') or line.startswith("'")
@@ -211,6 +213,9 @@ def sort_imports(
                 if not stripped_line or stripped_line.startswith("#"):
                     import_section += line
                 elif stripped_line.startswith(IMPORT_START_IDENTIFIERS):
+                    contains_imports = True
+
+                    indent = line[: -len(line.lstrip())]
                     import_section += line
                     while stripped_line.endswith("\\") or (
                         "(" in stripped_line and ")" not in stripped_line
@@ -225,8 +230,6 @@ def sort_imports(
                                 line = input_stream.readline()
                                 stripped_line = line.strip().split("#")[0]
                                 import_section += line
-
-                    contains_imports = True
                 else:
                     not_imports = True
 
@@ -243,12 +246,13 @@ def sort_imports(
                 add_imports = []
 
             if import_section:
-                if add_imports:
+                if add_imports and not indent:
                     import_section += line_separator.join(add_imports) + line_separator
                     contains_imports = True
                     add_imports = []
 
-                import_section += line
+                if not indent:
+                    import_section += line
                 if not contains_imports:
                     output_stream.write(import_section)
                 else:
@@ -257,11 +261,25 @@ def sort_imports(
                     ).startswith(COMMENT_INDICATORS):
                         import_section = import_section.lstrip(line_separator)
                         first_import_section = False
-                    output_stream.write(
-                        output.sorted_imports(
-                            parse.file_contents(import_section, config=config), config, extension
+
+                    if indent:
+                        import_section = line_separator.join(
+                            line.lstrip() for line in import_section.split(line_separator)
                         )
+                    sorted_import_section = output.sorted_imports(
+                        parse.file_contents(import_section, config=config), config, extension
                     )
+                    if indent:
+                        sorted_import_section = (
+                            textwrap.indent(sorted_import_section, indent) + line_separator
+                        )
+
+                    output_stream.write(sorted_import_section)
+
+                if indent:
+                    output_stream.write(line)
+                    indent = ""
+
                 contains_imports = False
                 import_section = ""
             else:
diff --git a/isort/finders.py b/isort/finders.py
index d63cc94b..5a7c74a7 100644
--- a/isort/finders.py
+++ b/isort/finders.py
@@ -30,16 +30,19 @@ from .utils import chdir, exists_case_sensitive
 
 try:
     from pipreqs import pipreqs
+
 except ImportError:
     pipreqs = None
 
 try:
     from pip_api import parse_requirements
+
 except ImportError:
     parse_requirements = None
 
 try:
     from requirementslib import Pipfile
+
 except ImportError:
     Pipfile = None
 
diff --git a/isort/settings.py b/isort/settings.py
index 607c67b6..1bdc35cb 100644
--- a/isort/settings.py
+++ b/isort/settings.py
@@ -43,6 +43,7 @@ from .wrap_modes import from_string as wrap_mode_from_string
 
 try:
     import toml
+
 except ImportError:
     toml = None  # type: ignore
 


Test patch:
diff --git a/tests/test_isort.py b/tests/test_isort.py
index 4e3a0332..40b22bb2 100644
--- a/tests/test_isort.py
+++ b/tests/test_isort.py
@@ -2922,6 +2922,7 @@ def test_to_ensure_importing_from_imports_module_works_issue_662() -> None:
         "@wraps(fun)\n"
         "def __inner(*args, **kwargs):\n"
         "    from .imports import qualname\n"
+        "\n"
         "    warn(description=description or qualname(fun), deprecation=deprecation, "
         "removal=removal)\n"
     )
@@ -4157,3 +4158,31 @@ def test_isort_with_single_character_import() -> None:
     """
     test_input = "from django.db.models import CASCADE, SET_NULL, Q\n"
     assert SortImports(file_contents=test_input).output == test_input
+
+
+def test_isort_nested_imports() -> None:
+    """Ensure imports in a nested block get sorted correctly"""
+    test_input = """
+    def import_test():
+        import sys
+        import os
+
+        # my imports
+        from . import def
+        from . import abc
+
+        return True
+    """
+    assert (
+        SortImports(file_contents=test_input).output
+        == """
+    def import_test():
+        import os
+        import sys
+
+        # my imports
+        from . import abc, def
+
+        return True
+    """
+    )