Repository: timothycrosley/isort
Pull request #620 (2017-12-21 13:02:43)

GH512: Add an option to ignore order within a single import statement
Resolves issue: timothycrosley/isort#512


Patch:
diff --git a/isort/isort.py b/isort/isort.py
index 99c3b23d..0b12767f 100644
--- a/isort/isort.py
+++ b/isort/isort.py
@@ -403,8 +403,9 @@ class SortImports(object):
                 continue
 
             import_start = "from {0} import ".format(module)
-            from_imports = self.imports[section]['from'][module]
-            from_imports = nsorted(from_imports, key=lambda key: self._module_key(key, self.config, True, ignore_case))
+            from_imports = list(self.imports[section]['from'][module])
+            if not self.config['no_inline_sort'] or self.config['force_single_line']:
+                from_imports = nsorted(from_imports, key=lambda key: self._module_key(key, self.config, True, ignore_case))
             if self.remove_imports:
                 from_imports = [line for line in from_imports if not "{0}.{1}".format(module, line) in
                                 self.remove_imports]
diff --git a/isort/main.py b/isort/main.py
index e6a84914..028693c2 100755
--- a/isort/main.py
+++ b/isort/main.py
@@ -167,6 +167,7 @@ class ISortCommand(setuptools.Command):
 def create_parser():
     parser = argparse.ArgumentParser(description='Sort Python import definitions alphabetically '
                                                  'within logical sections.')
+    inline_args_group = parser.add_mutually_exclusive_group()
     parser.add_argument('files', nargs='*', help='One or more Python source files that need their imports sorted.')
     parser.add_argument('-y', '--apply', dest='apply', action='store_true',
                         help='Tells isort to apply changes recursively without asking')
@@ -214,7 +215,7 @@ def create_parser():
                              'command line without modifying the file.')
     parser.add_argument('-ws', '--ignore-whitespace', action='store_true', dest="ignore_whitespace",
                         help='Tells isort to ignore whitespace differences when --check-only is being used.')
-    parser.add_argument('-sl', '--force-single-line-imports', dest='force_single_line', action='store_true',
+    inline_args_group.add_argument('-sl', '--force-single-line-imports', dest='force_single_line', action='store_true',
                         help='Forces all from imports to appear on their own line')
     parser.add_argument('-ds', '--no-sections', help='Put all imports into the same section bucket', dest='no_sections',
                         action='store_true')
@@ -268,7 +269,9 @@ def create_parser():
     parser.add_argument('-lbt', '--lines-between-types', dest='lines_between_types', type=int)
     parser.add_argument('-lai', '--lines-after-imports', dest='lines_after_imports', type=int)
     parser.add_argument('-up', '--use-parentheses', dest='use_parentheses', action='store_true',
-                        help='Use parenthesis for line continuation on length limit instead of slashes.')
+                        help='Use parenthesis for line continuation on lenght limit instead of slashes.')
+    inline_args_group.add_argument('-nis', '--no-inline-sort', dest='no_inline_sort', action='store_true',
+                        help='Leaves `from` imports with multiple imports \'as-is\' (e.g. `from foo import a, c ,b`).')
     parser.add_argument('-nlb', '--no-lines-before', help='Sections which should not be split with previous by empty lines',
                         dest='no_lines_before', action='append')
     parser.add_argument('-j', '--jobs', help='Number of files to process in parallel.',
diff --git a/isort/settings.py b/isort/settings.py
index c2b72455..c940726f 100644
--- a/isort/settings.py
+++ b/isort/settings.py
@@ -135,7 +135,8 @@ default = {'force_to_top': [],
            'force_sort_within_sections': False,
            'show_diff': False,
            'ignore_whitespace': False,
-           'no_lines_before': []}
+           'no_lines_before': [],
+           'no_inline_sort': False}
 
 
 @lru_cache()


Test patch:
diff --git a/test_isort.py b/test_isort.py
index de4f8c9c..a2a7348b 100644
--- a/test_isort.py
+++ b/test_isort.py
@@ -2074,7 +2074,7 @@ def test_ignore_whitespace(capsys):
 
 
 def test_import_wraps_with_comment_issue_471():
-    """Test to insure issue #471 is resolved"""
+    """Test to ensure issue #471 is resolved"""
     test_input = ('from very_long_module_name import SuperLongClassName  #@UnusedImport'
                   ' -- long string of comments which wrap over')
     expected_output = ('from very_long_module_name import (\n'
@@ -2238,3 +2238,18 @@ def test_not_splitted_sections():
            )
     assert SortImports(file_contents=test_input, no_lines_before=['FIRSTPARTY', 'LOCALFOLDER']).output == \
            (stdlib_section + firstparty_section + local_section + whiteline + statement)
+
+
+def test_no_inline_sort():
+    """Test to ensure multiple `from` imports in one line are not sorted if `--no-inline-sort` flag
+    is enabled. If `--force-single-line-imports` flag is enabled, then `--no-inline-sort` is ignored."""
+    test_input = 'from foo import a, c, b\n'
+    assert SortImports(file_contents=test_input, no_inline_sort=True, force_single_line=False).output == test_input
+    assert SortImports(file_contents=test_input, no_inline_sort=False, force_single_line=False).output == 'from foo import a, b, c\n'
+    expected = (
+        'from foo import a\n'
+        'from foo import b\n'
+        'from foo import c\n'
+    )
+    assert SortImports(file_contents=test_input, no_inline_sort=False, force_single_line=True).output == expected
+    assert SortImports(file_contents=test_input, no_inline_sort=True, force_single_line=True).output == expected