Repository: timothycrosley/isort (python)

Issue:
How do I set the config for one import per line with hanging vertical formatting?
What config options do I set to get the following results:

When there is a single import from a library it should be on one line and where there are multiple imports they should be using the hanging vertical formatting (#3). I can only seem to force this behavior with the line_length option, but that often results in a \ being used as a continuation.

```
from my.very.long.import.path import module
from os import path
from sys import (
    argv,
    stdout,
)
```

and not

```
from my.very.long.import.path \
    import module
from os import \
    path
from sys import (
    argv,
    stdout,
)
```

this is the config I am currently using
```
[isort]
add_imports=__future__.absolute_import
default_section=THIRDPARTY
import_heading_firstparty=Project Library
import_heading_stdlib=Standard Library
import_heading_thirdparty=External Libraries
indent=4
known_third_party=django,celery,disqus
line_length=1
multi_line_output=3
diff=1
```

Pull request description:
Support forcing line wrap for from imports
fixes #217

Patch:
diff --git a/isort/isort.py b/isort/isort.py
index 6b6518eb..a872d1fa 100644
--- a/isort/isort.py
+++ b/isort/isort.py
@@ -379,32 +379,34 @@ class SortImports(object):
                         if not from_imports:
                             import_statement = ""
                         if len(import_statement) > self.config['line_length']:
-                            if len(from_imports) > 1:
-                                output_mode = settings.WrapModes._fields[self.config.get('multi_line_output',
-                                                                                         0)].lower()
-                                formatter = getattr(self, "_output_" + output_mode, self._output_grid)
-                                dynamic_indent = " " * (len(import_start) + 1)
-                                indent = self.config['indent']
-                                line_length = self.config['line_length']
-                                import_statement = formatter(import_start, copy.copy(from_imports),
-                                                            dynamic_indent, indent, line_length, comments)
-                                if self.config['balanced_wrapping']:
-                                    lines = import_statement.split("\n")
-                                    line_count = len(lines)
-                                    if len(lines) > 1:
-                                        minimum_length = min([len(line) for line in lines[:-1]])
-                                    else:
-                                        minimum_length = 0
-                                    new_import_statement = import_statement
-                                    while (len(lines[-1]) < minimum_length and
-                                           len(lines) == line_count and line_length > 10):
-                                        import_statement = new_import_statement
-                                        line_length -= 1
-                                        new_import_statement = formatter(import_start, copy.copy(from_imports),
-                                                                        dynamic_indent, indent, line_length, comments)
-                                        lines = new_import_statement.split("\n")
-                            else:
-                                import_statement = self._wrap(import_statement)
+                            import_statement = self._wrap(import_statement)
+                        if len(from_imports) > 1 and (
+                            len(import_statement) > self.config['line_length']
+                            or self.config.get('force_from_wrap')
+                        ):
+                            output_mode = settings.WrapModes._fields[self.config.get('multi_line_output',
+                                                                                     0)].lower()
+                            formatter = getattr(self, "_output_" + output_mode, self._output_grid)
+                            dynamic_indent = " " * (len(import_start) + 1)
+                            indent = self.config['indent']
+                            line_length = self.config['line_length']
+                            import_statement = formatter(import_start, copy.copy(from_imports),
+                                                        dynamic_indent, indent, line_length, comments)
+                            if self.config['balanced_wrapping']:
+                                lines = import_statement.split("\n")
+                                line_count = len(lines)
+                                if len(lines) > 1:
+                                    minimum_length = min([len(line) for line in lines[:-1]])
+                                else:
+                                    minimum_length = 0
+                                new_import_statement = import_statement
+                                while (len(lines[-1]) < minimum_length and
+                                       len(lines) == line_count and line_length > 10):
+                                    import_statement = new_import_statement
+                                    line_length -= 1
+                                    new_import_statement = formatter(import_start, copy.copy(from_imports),
+                                                                    dynamic_indent, indent, line_length, comments)
+                                    lines = new_import_statement.split("\n")
 
 
                     if import_statement:
diff --git a/isort/main.py b/isort/main.py
index 2d1a92f8..d28908a4 100755
--- a/isort/main.py
+++ b/isort/main.py
@@ -104,6 +104,8 @@ def main():
                         help='Shows verbose output, such as when files are skipped or when a check is successful.')
     parser.add_argument('-sp', '--settings-path',  dest="settings_path",
                         help='Explicitly set the settings path instead of auto determining based on file location.')
+    parser.add_argument('-wf', '--force-from-wrap', action='store_true', dest='force_from_wrap', default='false',
+                        help='Force multiple from imports to be grid wrapped regardless of line length')
 
     arguments = dict((key, value) for (key, value) in itemsview(vars(parser.parse_args())) if value)
     file_names = arguments.pop('files', [])


Test patch:
diff --git a/test_isort.py b/test_isort.py
index 97846394..43264ce2 100644
--- a/test_isort.py
+++ b/test_isort.py
@@ -1115,3 +1115,21 @@ def test_sticky_comments():
                   "# Used for type-hinting (ref: https://github.com/davidhalter/jedi/issues/414).\n"
                   "from selenium.webdriver.remote.webdriver import WebDriver  # noqa\n")
     assert SortImports(file_contents=test_input).output == test_input
+
+
+def test_force_grid_wrap():
+    """Ensures removing imports works as expected."""
+    test_input = ("from foo import lib6, lib7\n"
+                  "from bar import lib2\n")
+    test_output = SortImports(
+      file_contents=test_input,
+      force_from_wrap=True,
+      multi_line_output=WrapModes.VERTICAL_HANGING_INDENT
+      ).output
+    print(test_output)
+    assert test_output == """from bar import lib2
+from foo import (
+    lib6,
+    lib7
+)
+"""