Repository: timothycrosley/isort
Pull request #632 (2018-01-19 16:28:27)

Smart newlines no longer skips multi-line string assignments
Prior to this change, the smart newline code was doing the wrong thing when
faced with code like this:

```
from x import y

something = """
test"""

class Blah():
    pass
```

The smart new lines code attempts to find the first construct after the import
block, and uses the `_skip_line` function to determine where that construct
starts.  However, `_skip_line` seems to incorrectly assume that a line ending
with `"""` is a docstring.   It then goes on to treat `class Blah` as the first
construct, and adds 2 new lines after imports instead of the 1 that should be
chosen.

I've fixed this by updating `_skip_lines` to stop iterating over the characters
of a line if it finds something that is neither whitespace, the start of a
string or a comment.


Patch:
diff --git a/.env b/.env
index 4f53fc5c..a7dbe409 100644
--- a/.env
+++ b/.env
@@ -16,18 +16,17 @@ if [ ! -d "venv" ]; then
      if ! hash pyvenv 2>/dev/null; then
         function pyvenv()
         {
-            if hash pyvenv-3.6 2>/dev/null; then
-                pyvenv-3.6 $@
-            elif hash pyvenv-3.5 2>/dev/null; then
+            if hash pyvenv-3.5 2>/dev/null; then
                 pyvenv-3.5 $@
-            elif hash pyvenv-3.4 2>/dev/null; then
+            fi
+            if hash pyvenv-3.4 2>/dev/null; then
                 pyvenv-3.4 $@
-            elif hash pyvenv-3.3 2>/dev/null; then
+            fi
+            if hash pyvenv-3.3 2>/dev/null; then
                 pyvenv-3.3 $@
-            elif hash pyvenv-3.2 2>/dev/null; then
+            fi
+            if hash pyvenv-3.2 2>/dev/null; then
                 pyvenv-3.2 $@
-            else
-                python3 -m venv $@
             fi
         }
     fi
@@ -86,12 +85,6 @@ function interact {
 }
 
 
-function open {
-    (root
-     $CODE_EDITOR isort/*.py setup.py test_*.py README.md tox.ini .gitignore CHANGELOG.md setup.cfg .editorconfig .env .coveragerc .travis.yml requirements.txt)
-}
-
-
 function _leave_project()
 {
     export PROJECT_NAME=""
@@ -106,7 +99,6 @@ function _leave_project()
 
     unset -f load
     unset -f interact
-    unset -f open
 
     deactivate
 }
diff --git a/.travis.yml b/.travis.yml
index ae6cbac9..6b23c0db 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,13 +1,14 @@
 sudo: false
 language: python
-cache: pip
 matrix:
   include:
   - env: TOXENV=isort-check
-  - python: 3.6
-    env: TOXENV=lint
+  - python: 2.6
+    env: TOXENV=py26
   - python: 2.7
     env: TOXENV=py27
+  - python: 3.3
+    env: TOXENV=py33
   - python: 3.4
     env: TOXENV=py34
   - python: 3.5
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 94189b3a..bd70e3e4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,14 +1,5 @@
 Changelog
 =========
-### 4.3.0 - In progress
-- Fixed #557: `force_alphabetical_sort` and `force_sort_within_sections` can now be utilized together without extra new lines
-- Fix case-sensitive path existence check in Mac OS X
-- Added `--no-lines-before` for more granular control over section output
-- Fixed #493: Unwanted conversion to Windows line endings 
-- Implemented `--version-number` to retrieve just the version number without the isort logo
-- Breaking changes
-    - Python 2.7+ only (dropped 2.6) allowing various code simplifications and improvements.
-
 ### 4.2.15 - June 6, 2017 - hotfix release
 IMPORTANT NOTE: This will be the last release with Python 2.6 support, subsequent releases will be 2.7+ only
 - Fixed certain one line imports not being successfully wrapped
diff --git a/README.rst b/README.rst
index bc83c515..f185178d 100644
--- a/README.rst
+++ b/README.rst
@@ -4,7 +4,7 @@
 ########
 
 .. image:: https://badge.fury.io/py/isort.svg
-    :target: https://badge.fury.io/py/isort
+    :target: http://badge.fury.io/py/isort
     :alt: PyPI version
 
 .. image:: https://travis-ci.org/timothycrosley/isort.svg?branch=master
@@ -29,7 +29,7 @@ isort your python imports for you so you don't have to.
 
 isort is a Python utility / library to sort imports alphabetically, and automatically separated into sections.
 It provides a command line utility, Python library and `plugins for various editors <https://github.com/timothycrosley/isort/wiki/isort-Plugins>`_ to quickly sort all your imports.
-It currently cleanly supports Python 2.7 - 3.6 without any dependencies.
+It currently cleanly supports Python 2.6 - 3.5 without any dependencies.
 
 .. image:: https://raw.github.com/timothycrosley/isort/develop/example.gif
    :alt: Example Usage
diff --git a/appveyor.yml b/appveyor.yml
index 48207e2d..003d2e58 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -9,4 +9,4 @@ install:
   - pip install tox
 
 test_script:
-  - tox -e isort-check,py27,py34,py35,py36
+  - tox -e isort-check,py26,py27,py33,py34,py35,py36
diff --git a/isort/__init__.py b/isort/__init__.py
index 13a8549a..4dd2cd16 100644
--- a/isort/__init__.py
+++ b/isort/__init__.py
@@ -22,7 +22,7 @@ OTHER DEALINGS IN THE SOFTWARE.
 
 from __future__ import absolute_import, division, print_function, unicode_literals
 
-from . import settings  # noqa: F401
-from .isort import SortImports  # noqa: F401
+from . import settings
+from .isort import SortImports
 
 __version__ = "4.2.14"
diff --git a/isort/isort.py b/isort/isort.py
index 99c3b23d..a5381594 100644
--- a/isort/isort.py
+++ b/isort/isort.py
@@ -32,8 +32,7 @@ import itertools
 import os
 import re
 import sys
-import sysconfig
-from collections import OrderedDict, namedtuple
+from collections import namedtuple
 from datetime import datetime
 from difflib import unified_diff
 from fnmatch import fnmatch
@@ -41,7 +40,7 @@ from glob import glob
 
 from . import settings
 from .natural import nsorted
-from .pie_slice import OrderedSet, input, itemsview
+from .pie_slice import OrderedDict, OrderedSet, input, itemsview
 
 KNOWN_SECTION_MAPPING = {
     'STDLIB': 'STANDARD_LIBRARY',
@@ -88,8 +87,6 @@ class SortImports(object):
                 indent = "\t"
         self.config['indent'] = indent
 
-        self.config['comment_prefix'] = self.config['comment_prefix'].strip("'").strip('"')
-
         self.place_imports = {}
         self.import_placements = {}
         self.remove_imports = [self._format_simplified(removal) for removal in self.config['remove_imports']]
@@ -111,23 +108,13 @@ class SortImports(object):
             elif not file_contents:
                 self.file_path = file_path
                 self.file_encoding = coding_check(file_path)
-                with io.open(file_path, encoding=self.file_encoding, newline='') as file_to_import_sort:
+                with io.open(file_path, encoding=self.file_encoding) as file_to_import_sort:
                     file_contents = file_to_import_sort.read()
 
         if file_contents is None or ("isort:" + "skip_file") in file_contents:
-            self.skipped = True
             return
 
-        if self.config['line_ending']:
-            self.line_separator = self.config['line_ending']
-        else:
-            if '\r\n' in file_contents:
-                self.line_separator = '\r\n'
-            elif '\r' in file_contents:
-                self.line_separator = '\r'
-            else:
-                self.line_separator = '\n'
-        self.in_lines = file_contents.split(self.line_separator)
+        self.in_lines = file_contents.split("\n")
         self.original_length = len(self.in_lines)
         if (self.original_length > 1 or self.in_lines[:1] not in ([], [""])) or self.config['force_adds']:
             for add_import in self.add_imports:
@@ -160,20 +147,22 @@ class SortImports(object):
         self._parse()
         if self.import_index != -1:
             self._add_formatted_imports()
+
         self.length_change = len(self.out_lines) - self.original_length
         while self.out_lines and self.out_lines[-1].strip() == "":
             self.out_lines.pop(-1)
         self.out_lines.append("")
-        self.output = self.line_separator.join(self.out_lines)
+
+        self.output = "\n".join(self.out_lines)
         if self.config['atomic']:
             try:
-                compile(self._strip_top_comments(self.out_lines, self.line_separator), self.file_path, 'exec', 0, 1)
+                compile(self._strip_top_comments(self.out_lines), self.file_path, 'exec', 0, 1)
             except SyntaxError:
                 self.output = file_contents
                 self.incorrectly_sorted = True
                 try:
-                    compile(self._strip_top_comments(self.in_lines, self.line_separator), self.file_path, 'exec', 0, 1)
-                    print("ERROR: {0} isort would have introduced syntax errors, please report to the project!".
+                    compile(self._strip_top_comments(self.in_lines), self.file_path, 'exec', 0, 1)
+                    print("ERROR: {0} isort would have introduced syntax errors, please report to the project!". \
                           format(self.file_path))
                 except SyntaxError:
                     print("ERROR: {0} File contains syntax errors.".format(self.file_path))
@@ -183,8 +172,8 @@ class SortImports(object):
             check_output = self.output
             check_against = file_contents
             if self.config['ignore_whitespace']:
-                check_output = check_output.replace(self.line_separator, "").replace(" ", "")
-                check_against = check_against.replace(self.line_separator, "").replace(" ", "")
+                check_output = check_output.replace("\n", "").replace(" ", "")
+                check_against = check_against.replace("\n", "").replace(" ", "")
 
             if check_output == check_against:
                 if self.config['verbose']:
@@ -204,12 +193,12 @@ class SortImports(object):
                 self._show_diff(file_contents)
                 answer = None
                 while answer not in ('yes', 'y', 'no', 'n', 'quit', 'q'):
-                    answer = input("Apply suggested changes to '{0}' [y/n/q]? ".format(self.file_path)).lower()
+                    answer = input("Apply suggested changes to '{0}' [y/n/q]?".format(self.file_path)).lower()
                     if answer in ('no', 'n'):
                         return
                     if answer in ('quit', 'q'):
                         sys.exit(1)
-            with io.open(self.file_path, encoding=self.file_encoding, mode='w', newline=self.line_separator) as output_file:
+            with io.open(self.file_path, encoding=self.file_encoding, mode='w') as output_file:
                 output_file.write(self.output)
 
     def _show_diff(self, file_contents):
@@ -225,12 +214,12 @@ class SortImports(object):
             sys.stdout.write(line)
 
     @staticmethod
-    def _strip_top_comments(lines, line_separator):
+    def _strip_top_comments(lines):
         """Strips # comments that exist at the top of the given lines"""
         lines = copy.copy(lines)
         while lines and lines[0].startswith("#"):
             lines = lines[1:]
-        return line_separator.join(lines)
+        return "\n".join(lines)
 
     def place_module(self, module_name):
         """Tries to determine if a module is a python std import, third party import, or project code:
@@ -272,7 +261,7 @@ class SortImports(object):
             virtual_env_src = '{0}/src/'.format(virtual_env)
 
         # handle case-insensitive paths on windows
-        stdlib_lib_prefix = os.path.normcase(sysconfig.get_paths()['stdlib'])
+        stdlib_lib_prefix = os.path.normcase(get_stdlib_path())
 
         for prefix in paths:
             module_path = "/".join((prefix, module_name.replace(".", "/")))
@@ -334,8 +323,7 @@ class SortImports(object):
         """
             Returns a string with comments added
         """
-        return comments and "{0}{1} {2}".format(self._strip_comments(original_string)[0],
-                                                self.config['comment_prefix'],
+        return comments and "{0}  # {1}".format(self._strip_comments(original_string)[0],
                                                 "; ".join(comments)) or original_string
 
     def _wrap(self, line):
@@ -344,16 +332,10 @@ class SortImports(object):
         """
         wrap_mode = self.config['multi_line_output']
         if len(line) > self.config['line_length'] and wrap_mode != settings.WrapModes.NOQA:
-            line_without_comment = line
-            comment = None
-            if '#' in line:
-                line_without_comment, comment = line.split('#', 1)
-            for splitter in ("import ", ".", "as "):
+            for splitter in ("import", ".", "as"):
                 exp = r"\b" + re.escape(splitter) + r"\b"
-                if re.search(exp, line_without_comment) and not line_without_comment.strip().startswith(splitter):
-                    line_parts = re.split(exp, line_without_comment)
-                    if comment:
-                        line_parts[-1] = '{0}#{1}'.format(line_parts[-1], comment)
+                if re.search(exp, line) and not line.strip().startswith(splitter):
+                    line_parts = re.split(exp, line)
                     next_line = []
                     while (len(line) + 2) > (self.config['wrap_length'] or self.config['line_length']) and line_parts:
                         next_line.append(line_parts.pop())
@@ -363,22 +345,22 @@ class SortImports(object):
 
                     cont_line = self._wrap(self.config['indent'] + splitter.join(next_line).lstrip())
                     if self.config['use_parentheses']:
-                        output = "{0}{1}({2}{3}{4}{5})".format(
-                            line, splitter, self.line_separator, cont_line,
+                        output = "{0}{1} (\n{2}{3}{4})".format(
+                            line, splitter, cont_line,
                             "," if self.config['include_trailing_comma'] else "",
-                            self.line_separator if wrap_mode in (
+                            "\n" if wrap_mode in (
                                 settings.WrapModes.VERTICAL_HANGING_INDENT,
                                 settings.WrapModes.VERTICAL_GRID_GROUPED,
                             ) else "")
-                        lines = output.split(self.line_separator)
-                        if self.config['comment_prefix'] in lines[-1] and lines[-1].endswith(')'):
-                            line, comment = lines[-1].split(self.config['comment_prefix'], 1)
-                            lines[-1] = line + ')' + self.config['comment_prefix'] + comment[:-1]
-                        return self.line_separator.join(lines)
-                    return "{0}{1}\\{2}{3}".format(line, splitter, self.line_separator, cont_line)
+                        lines = output.split('\n')
+                        if '  #' in lines[-1] and lines[-1].endswith(')'):
+                            line, comment = lines[-1].split('  #', 1)
+                            lines[-1] = line + ')  #' + comment[:-1]
+                        return '\n'.join(lines)
+                    return "{0}{1} \\\n{2}".format(line, splitter, cont_line)
         elif len(line) > self.config['line_length'] and wrap_mode == settings.WrapModes.NOQA:
             if "# NOQA" not in line:
-                return "{0}{1} NOQA".format(line, self.config['comment_prefix'])
+                return "{0}  # NOQA".format(line)
 
         return line
 
@@ -409,43 +391,38 @@ class SortImports(object):
                 from_imports = [line for line in from_imports if not "{0}.{1}".format(module, line) in
                                 self.remove_imports]
 
-            sub_modules = ['{0}.{1}'.format(module, from_import) for from_import in from_imports]
-            as_imports = dict((from_import, "{0} as {1}".format(from_import, self.as_map[sub_module])) for
-                              from_import, sub_module in zip(from_imports, sub_modules) if sub_module in self.as_map)
-            if self.config['combine_as_imports'] and not ("*" in from_imports and self.config['combine_star']):
-                for from_import in copy.copy(from_imports):
-                    if from_import in as_imports:
-                        from_imports[from_imports.index(from_import)] = as_imports.pop(from_import)
+            for from_import in copy.copy(from_imports):
+                submodule = module + "." + from_import
+                import_as = self.as_map.get(submodule, False)
+                if import_as:
+                    import_definition = "{0} as {1}".format(from_import, import_as)
+                    if self.config['combine_as_imports'] and not ("*" in from_imports and
+                                                                    self.config['combine_star']):
+                        from_imports[from_imports.index(from_import)] = import_definition
+                    else:
+                        import_statement = import_start + import_definition
+                        force_grid_wrap = self.config['force_grid_wrap']
+                        comments = self.comments['straight'].get(submodule)
+                        import_statement = self._add_comments(comments, self._wrap(import_statement))
+                        from_imports.remove(from_import)
+                        section_output.append(import_statement)
+
 
-            while from_imports:
+            if from_imports:
                 comments = self.comments['from'].pop(module, ())
                 if "*" in from_imports and self.config['combine_star']:
                     import_statement = self._wrap(self._add_comments(comments, "{0}*".format(import_start)))
-                    from_imports = None
                 elif self.config['force_single_line']:
                     import_statements = []
-                    while from_imports:
-                        from_import = from_imports.pop(0)
-                        if from_import in as_imports:
-                            from_comments = self.comments['straight'].get('{}.{}'.format(module, from_import))
-                            import_statements.append(self._add_comments(from_comments,
-                                                     self._wrap(import_start + as_imports[from_import])))
-                            continue
+                    for from_import in from_imports:
                         single_import_line = self._add_comments(comments, import_start + from_import)
                         comment = self.comments['nested'].get(module, {}).pop(from_import, None)
                         if comment:
-                            single_import_line += "{0} {1}".format(comments and ";" or self.config['comment_prefix'],
-                                                                   comment)
+                            single_import_line += "{0} {1}".format(comments and ";" or "  #", comment)
                         import_statements.append(self._wrap(single_import_line))
                         comments = None
-                    import_statement = self.line_separator.join(import_statements)
+                    import_statement = "\n".join(import_statements)
                 else:
-                    while from_imports and from_imports[0] in as_imports:
-                        from_import = from_imports.pop(0)
-                        from_comments = self.comments['straight'].get('{}.{}'.format(module, from_import))
-                        section_output.append(self._add_comments(from_comments,
-                                                                 self._wrap(import_start + as_imports[from_import])))
-
                     star_import = False
                     if "*" in from_imports:
                         section_output.append(self._add_comments(comments, "{0}*".format(import_start)))
@@ -454,13 +431,10 @@ class SortImports(object):
                         comments = None
 
                     for from_import in copy.copy(from_imports):
-                        if from_import in as_imports:
-                            continue
                         comment = self.comments['nested'].get(module, {}).pop(from_import, None)
                         if comment:
                             single_import_line = self._add_comments(comments, import_start + from_import)
-                            single_import_line += "{0} {1}".format(comments and ";" or self.config['comment_prefix'],
-                                                                   comment)
+                            single_import_line += "{0} {1}".format(comments and ";" or "  #", comment)
                             above_comments = self.comments['above']['from'].pop(module, None)
                             if above_comments:
                                 section_output.extend(above_comments)
@@ -468,32 +442,29 @@ class SortImports(object):
                             from_imports.remove(from_import)
                             comments = None
 
-                    from_import_section = []
-                    while from_imports and from_imports[0] not in as_imports:
-                        from_import_section.append(from_imports.pop(0))
                     if star_import:
-                        import_statement = import_start + (", ").join(from_import_section)
+                        import_statement = import_start + (", ").join(from_imports)
                     else:
-                        import_statement = self._add_comments(comments, import_start + (", ").join(from_import_section))
-                    if not from_import_section:
+                        import_statement = self._add_comments(comments, import_start + (", ").join(from_imports))
+                    if not from_imports:
                         import_statement = ""
 
                     do_multiline_reformat = False
 
                     force_grid_wrap = self.config['force_grid_wrap']
-                    if force_grid_wrap and len(from_import_section) >= force_grid_wrap:
+                    if force_grid_wrap and len(from_imports) >= force_grid_wrap:
                         do_multiline_reformat = True
 
-                    if len(import_statement) > self.config['line_length'] and len(from_import_section) > 1:
+                    if len(import_statement) > self.config['line_length'] and len(from_imports) > 1:
                         do_multiline_reformat = True
 
                     # If line too long AND have imports AND we are NOT using GRID or VERTICAL wrap modes
-                    if (len(import_statement) > self.config['line_length'] and len(from_import_section) > 0 and
+                    if (len(import_statement) > self.config['line_length'] and len(from_imports) > 0 and
                             self.config['multi_line_output'] not in (1, 0)):
                         do_multiline_reformat = True
 
                     if do_multiline_reformat:
-                        import_statement = self._multi_line_reformat(import_start, from_import_section, comments)
+                        import_statement = self._multi_line_reformat(import_start, from_imports, comments)
                     if not do_multiline_reformat and len(import_statement) > self.config['line_length']:
                         import_statement = self._wrap(import_statement)
 
@@ -512,10 +483,10 @@ class SortImports(object):
         import_statement = formatter(import_start, copy.copy(from_imports),
                                      dynamic_indent, indent, line_length, comments)
         if self.config['balanced_wrapping']:
-            lines = import_statement.split(self.line_separator)
+            lines = import_statement.split("\n")
             line_count = len(lines)
             if len(lines) > 1:
-                minimum_length = min(len(line) for line in lines[:-1])
+                minimum_length = min([len(line) for line in lines[:-1]])
             else:
                 minimum_length = 0
             new_import_statement = import_statement
@@ -525,8 +496,8 @@ class SortImports(object):
                 line_length -= 1
                 new_import_statement = formatter(import_start, copy.copy(from_imports),
                                                 dynamic_indent, indent, line_length, comments)
-                lines = new_import_statement.split(self.line_separator)
-        if import_statement.count(self.line_separator) == 0:
+                lines = new_import_statement.split("\n")
+        if import_statement.count('\n') == 0:
             return self._wrap(import_statement)
         return import_statement
 
@@ -547,7 +518,6 @@ class SortImports(object):
             sections = ('no_sections', )
 
         output = []
-        prev_section_has_imports = False
         for section in sections:
             straight_modules = self.imports[section]['straight']
             straight_modules = nsorted(straight_modules, key=lambda key: self._module_key(key, self.config))
@@ -580,6 +550,7 @@ class SortImports(object):
                         line = line.lower()
                     return '{0}{1}'.format(section, line)
                 section_output = nsorted(section_output, key=by_module)
+
             if section_output:
                 section_name = section
                 if section_name in self.place_imports:
@@ -589,17 +560,12 @@ class SortImports(object):
                 section_title = self.config.get('import_heading_' + str(section_name).lower(), '')
                 if section_title:
                     section_comment = "# {0}".format(section_title)
-                    if section_comment not in self.out_lines[0:1] and section_comment not in self.in_lines[0:1]:
+                    if not section_comment in self.out_lines[0:1] and not section_comment in self.in_lines[0:1]:
                         section_output.insert(0, section_comment)
-                if prev_section_has_imports and section_name in self.config['no_lines_before']:
-                    while output and output[-1].strip() == '':
-                        output.pop()
                 output += section_output + ([''] * self.config['lines_between_sections'])
-            prev_section_has_imports = bool(section_output)
-        while output and output[-1].strip() == '':
+
+        while [character.strip() for character in output[-1:]] == [""]:
             output.pop()
-        while output and output[0].strip() == '':
-            output.pop(0)
 
         output_at = 0
         if self.import_index < self.original_length:
@@ -646,7 +612,7 @@ class SortImports(object):
         while imports:
             next_import = imports.pop(0)
             next_statement = self._add_comments(comments, statement + ", " + next_import)
-            if len(next_statement.split(self.line_separator)[-1]) + 1 > line_length:
+            if len(next_statement.split("\n")[-1]) + 1 > line_length:
                 lines = ['{0}{1}'.format(white_space, next_import.split(" ")[0])]
                 for part in next_import.split(" ")[1:]:
                     new_line = '{0} {1}'.format(lines[-1], part)
@@ -654,20 +620,20 @@ class SortImports(object):
                         lines.append('{0}{1}'.format(white_space, part))
                     else:
                         lines[-1] = new_line
-                next_import = self.line_separator.join(lines)
+                next_import = '\n'.join(lines)
                 statement = (self._add_comments(comments, "{0},".format(statement)) +
-                             "{0}{1}".format(self.line_separator, next_import))
+                             "\n{0}".format(next_import))
                 comments = None
             else:
                 statement += ", " + next_import
         return statement + ("," if self.config['include_trailing_comma'] else "") + ")"
 
     def _output_vertical(self, statement, imports, white_space, indent, line_length, comments):
-        first_import = self._add_comments(comments, imports.pop(0) + ",") + self.line_separator + white_space
+        first_import = self._add_comments(comments, imports.pop(0) + ",") + "\n" + white_space
         return "{0}({1}{2}{3})".format(
             statement,
             first_import,
-            ("," + self.line_separator + white_space).join(imports),
+            (",\n" + white_space).join(imports),
             "," if self.config['include_trailing_comma'] else "",
         )
 
@@ -676,30 +642,29 @@ class SortImports(object):
         while imports:
             next_import = imports.pop(0)
             next_statement = self._add_comments(comments, statement + ", " + next_import)
-            if len(next_statement.split(self.line_separator)[-1]) + 3 > line_length:
+            if len(next_statement.split("\n")[-1]) + 3 > line_length:
                 next_statement = (self._add_comments(comments, "{0}, \\".format(statement)) +
-                                  "{0}{1}{2}".format(self.line_separator, indent, next_import))
+                                  "\n{0}{1}".format(indent, next_import))
                 comments = None
             statement = next_statement
         return statement
 
     def _output_vertical_hanging_indent(self, statement, imports, white_space, indent, line_length, comments):
-        return "{0}({1}{2}{3}{4}{5}{2})".format(
+        return "{0}({1}\n{2}{3}{4}\n)".format(
             statement,
             self._add_comments(comments),
-            self.line_separator,
             indent,
-            ("," + self.line_separator + indent).join(imports),
+            (",\n" + indent).join(imports),
             "," if self.config['include_trailing_comma'] else "",
          )
 
     def _output_vertical_grid_common(self, statement, imports, white_space, indent, line_length, comments):
-        statement += self._add_comments(comments, "(") + self.line_separator + indent + imports.pop(0)
+        statement += self._add_comments(comments, "(") + "\n" + indent + imports.pop(0)
         while imports:
             next_import = imports.pop(0)
             next_statement = "{0}, {1}".format(statement, next_import)
-            if len(next_statement.split(self.line_separator)[-1]) + 1 > line_length:
-                next_statement = "{0},{1}{2}{3}".format(statement, self.line_separator, indent, next_import)
+            if len(next_statement.split("\n")[-1]) + 1 > line_length:
+                next_statement = "{0},\n{1}{2}".format(statement, indent, next_import)
             statement = next_statement
         if self.config['include_trailing_comma']:
             statement += ','
@@ -709,25 +674,24 @@ class SortImports(object):
         return self._output_vertical_grid_common(statement, imports, white_space, indent, line_length, comments) + ")"
 
     def _output_vertical_grid_grouped(self, statement, imports, white_space, indent, line_length, comments):
-        return self._output_vertical_grid_common(statement, imports, white_space, indent, line_length, comments) \
-               + self.line_separator + ")"
+        return self._output_vertical_grid_common(statement, imports, white_space, indent, line_length, comments) + "\n)"
 
     def _output_noqa(self, statement, imports, white_space, indent, line_length, comments):
         retval = '{0}{1}'.format(statement, ', '.join(imports))
         comment_str = ' '.join(comments)
         if comments:
-            if len(retval) + len(self.config['comment_prefix']) + 1 + len(comment_str) <= line_length:
-                return '{0}{1} {2}'.format(retval, self.config['comment_prefix'], comment_str)
+            if len(retval) + 4 + len(comment_str) <= line_length:
+                return '{0}  # {1}'.format(retval, comment_str)
         else:
             if len(retval) <= line_length:
                 return retval
         if comments:
             if "NOQA" in comments:
-                return '{0}{1} {2}'.format(retval, self.config['comment_prefix'], comment_str)
+                return '{0}  # {1}'.format(retval, comment_str)
             else:
-                return '{0}{1} NOQA {2}'.format(retval, self.config['comment_prefix'], comment_str)
+                return '{0}  # NOQA {1}'.format(retval, comment_str)
         else:
-            return '{0}{1} NOQA'.format(retval, self.config['comment_prefix'])
+            return '{0}  # NOQA'.format(retval)
 
     @staticmethod
     def _strip_comments(line, comments=None):
@@ -759,7 +723,7 @@ class SortImports(object):
     def _format_natural(import_line):
         import_line = import_line.strip()
         if not import_line.startswith("from ") and not import_line.startswith("import "):
-            if "." not in import_line:
+            if not "." in import_line:
                 return "import {0}".format(import_line)
             parts = import_line.split(".")
             end = parts.pop(-1)
@@ -798,6 +762,8 @@ class SortImports(object):
                         self._in_quote = line[index]
                 elif line[index] == "#":
                     break
+                elif line[index] not in (' ', '\t'):
+                    break
                 index += 1
 
         return skip_line or self._in_quote or self._in_top_comment
@@ -856,6 +822,7 @@ class SortImports(object):
                 nested_comments = {}
                 import_string, comments, new_comments = self._strip_comments(line)
                 stripped_line = [part for part in self._strip_syntax(import_string).strip().split(" ") if part]
+
                 if import_type == "from" and len(stripped_line) == 2 and stripped_line[1] != "*" and new_comments:
                     nested_comments[stripped_line[-1]] = comments[0]
 
@@ -863,17 +830,17 @@ class SortImports(object):
                     while not line.strip().endswith(")") and not self._at_end():
                         line, comments, new_comments = self._strip_comments(self._get_line(), comments)
                         stripped_line = self._strip_syntax(line).strip()
-                        if import_type == "from" and stripped_line and " " not in stripped_line and new_comments:
+                        if import_type == "from" and stripped_line and not " " in stripped_line and new_comments:
                             nested_comments[stripped_line] = comments[-1]
-                        import_string += self.line_separator + line
+                        import_string += "\n" + line
                 else:
                     while line.strip().endswith("\\"):
                         line, comments, new_comments = self._strip_comments(self._get_line(), comments)
                         stripped_line = self._strip_syntax(line).strip()
-                        if import_type == "from" and stripped_line and " " not in stripped_line and new_comments:
+                        if import_type == "from" and stripped_line and not " " in stripped_line and new_comments:
                             nested_comments[stripped_line] = comments[-1]
                         if import_string.strip().endswith(" import") or line.strip().startswith("import "):
-                            import_string += self.line_separator + line
+                            import_string += "\n" + line
                         else:
                             import_string = import_string.rstrip().rstrip("\\") + " " + line.lstrip()
 
@@ -901,8 +868,6 @@ class SortImports(object):
                 if import_type == "from":
                     import_from = imports.pop(0)
                     placed_module = self.place_module(import_from)
-                    if self.config['verbose']:
-                        print("from-type place_module for %s returned %s" % (import_from, placed_module))
                     if placed_module == '':
                         print(
                             "WARNING: could not place module {0} of line {1} --"
@@ -919,8 +884,8 @@ class SortImports(object):
 
                     if len(self.out_lines) > max(self.import_index, self._first_comment_index_end + 1, 1) - 1:
                         last = self.out_lines and self.out_lines[-1].rstrip() or ""
-                        while (last.startswith("#") and not last.endswith('"""') and not last.endswith("'''") and
-                               'isort:imports-' not in last):
+                        while (last.startswith("#") and not last.endswith('"""') and not last.endswith("'''") and not
+                               'isort:imports-' in last):
                             self.comments['above']['from'].setdefault(import_from, []).insert(0, self.out_lines.pop(-1))
                             if len(self.out_lines) > max(self.import_index - 1, self._first_comment_index_end + 1, 1) - 1:
                                 last = self.out_lines[-1].rstrip()
@@ -943,7 +908,7 @@ class SortImports(object):
 
                             last = self.out_lines and self.out_lines[-1].rstrip() or ""
                             while (last.startswith("#") and not last.endswith('"""') and not last.endswith("'''") and
-                                   'isort:imports-' not in last):
+                                   not 'isort:imports-' in last):
                                 self.comments['above']['straight'].setdefault(module, []).insert(0,
                                                                                                  self.out_lines.pop(-1))
                                 if len(self.out_lines) > 0:
@@ -953,8 +918,6 @@ class SortImports(object):
                             if self.index - 1 == self.import_index:
                                 self.import_index -= len(self.comments['above']['straight'].get(module, []))
                         placed_module = self.place_module(module)
-                        if self.config['verbose']:
-                            print("else-type place_module for %s returned %s" % (module, placed_module))
                         if placed_module == '':
                             print(
                                 "WARNING: could not place module {0} of line {1} --"
@@ -981,6 +944,18 @@ def coding_check(fname, default='utf-8'):
     return coding
 
 
+def get_stdlib_path():
+    """Returns the path to the standard lib for the current path installation.
+
+    This function can be dropped and "sysconfig.get_paths()" used directly once Python 2.6 support is dropped.
+    """
+    if sys.version_info >= (2, 7):
+        import sysconfig
+        return sysconfig.get_paths()['stdlib']
+    else:
+        return os.path.join(sys.prefix, 'lib')
+
+
 def exists_case_sensitive(path):
     """
     Returns if the given path exists and also matches the case on Windows.
@@ -990,7 +965,7 @@ def exists_case_sensitive(path):
     can only import using the case of the real file.
     """
     result = os.path.exists(path)
-    if (sys.platform.startswith('win') or sys.platform == 'darwin') and result:
+    if sys.platform.startswith('win') and result:
         directory, basename = os.path.split(path)
         result = basename in os.listdir(directory)
     return result
diff --git a/isort/main.py b/isort/main.py
index e6a84914..eae7afa5 100755
--- a/isort/main.py
+++ b/isort/main.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#! /usr/bin/env python
 '''  Tool for sorting imports alphabetically, and automatically separated into sections.
 
 Copyright (C) 2013  Timothy Edmund Crosley
@@ -23,10 +23,7 @@ from __future__ import absolute_import, division, print_function, unicode_litera
 import argparse
 import glob
 import os
-import re
 import sys
-from concurrent.futures import ProcessPoolExecutor
-import functools
 
 import setuptools
 
@@ -35,6 +32,7 @@ from isort.settings import DEFAULT_SECTIONS, default, from_path, should_skip
 
 from .pie_slice import itemsview
 
+
 INTRO = r"""
 /#######################################################################\
 
@@ -58,32 +56,6 @@ INTRO = r"""
 \########################################################################/
 """.format(__version__)
 
-shebang_re = re.compile(br'^#!.*\bpython[23w]?\b')
-
-
-def is_python_file(path):
-    if path.endswith('.py'):
-        return True
-
-    with open(path, 'rb') as fp:
-        line = fp.readline(100)
-    return bool(shebang_re.match(line))
-
-
-class SortAttempt(object):
-    def __init__(self, incorrectly_sorted, skipped):
-        self.incorrectly_sorted = incorrectly_sorted
-        self.skipped = skipped
-
-
-def sort_imports(file_name, **arguments):
-    try:
-        result = SortImports(file_name, **arguments)
-        return SortAttempt(result.incorrectly_sorted, result.skipped)
-    except IOError as e:
-        print("WARNING: Unable to parse file {0} due to {1}".format(file_name, e))
-        return None
-
 
 def iter_source_code(paths, config, skipped):
     """Iterate over all Python source files defined in paths."""
@@ -99,12 +71,11 @@ def iter_source_code(paths, config, skipped):
                         skipped.append(dirname)
                         dirnames.remove(dirname)
                 for filename in filenames:
-                    filepath = os.path.join(dirpath, filename)
-                    if is_python_file(filepath):
+                    if filename.endswith('.py'):
                         if should_skip(filename, config, dirpath):
                             skipped.append(filename)
                         else:
-                            yield filepath
+                            yield os.path.join(dirpath, filename)
         else:
             yield path
 
@@ -240,8 +211,6 @@ def create_parser():
                         help="Combines as imports on the same line.")
     parser.add_argument('-tc', '--trailing-comma', dest='include_trailing_comma', action='store_true',
                         help='Includes a trailing comma on multi line imports that include parentheses.')
-    parser.add_argument('-vn', '--version-number', action='version', version=__version__,
-                        help='Returns just the current version number without the logo')
     parser.add_argument('-v', '--version', action='store_true', dest='show_version')
     parser.add_argument('-vb', '--verbose', action='store_true', dest="verbose",
                         help='Shows verbose output, such as when files are skipped or when a check is successful.')
@@ -253,8 +222,6 @@ def create_parser():
                         help="Switches the typical ordering preference, showing from imports first then straight ones.")
     parser.add_argument('-wl', '--wrap-length', dest='wrap_length',
                         help="Specifies how long lines that are wrapped should be, if not set line_length is used.")
-    parser.add_argument('-le', '--line-ending', dest='line_ending',
-                        help="Forces line endings to the specified value. If not set, values will be guessed per-file.")
     parser.add_argument('-fgw', '--force-grid-wrap', nargs='?', const=2, type=int, dest="force_grid_wrap",
                         help='Force number of from imports (defaults to 2) to be grid wrapped regardless of line '
                              'length')
@@ -266,15 +233,10 @@ def create_parser():
     parser.add_argument('-fss', '--force-sort-within-sections', action='store_true', dest="force_sort_within_sections",
                         help='Force imports to be sorted by module, independent of import_type')
     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.')
-    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.',
-                        dest='jobs', type=int)
+                        help='Use parenthesis for line continuation on lenght limit instead of slashes.')
 
-    arguments = {key: value for key, value in itemsview(vars(parser.parse_args())) if value}
+    arguments = dict((key, value) for (key, value) in itemsview(vars(parser.parse_args())) if value)
     if 'dont_order_by_type' in arguments:
         arguments['order_by_type'] = False
     return arguments
@@ -308,29 +270,16 @@ def main():
         num_skipped = 0
         if config['verbose'] or config.get('show_logo', False):
             print(INTRO)
-        jobs = arguments.get('jobs')
-        if jobs:
-            executor = ProcessPoolExecutor(max_workers=jobs)
-
-            for sort_attempt in executor.map(functools.partial(sort_imports, **arguments), file_names):
-                if not sort_attempt:
-                    continue
+        for file_name in file_names:
+            try:
+                sort_attempt = SortImports(file_name, **arguments)
                 incorrectly_sorted = sort_attempt.incorrectly_sorted
                 if arguments.get('check', False) and incorrectly_sorted:
                     wrong_sorted_files = True
                 if sort_attempt.skipped:
                     num_skipped += 1
-        else:
-            for file_name in file_names:
-                try:
-                    sort_attempt = SortImports(file_name, **arguments)
-                    incorrectly_sorted = sort_attempt.incorrectly_sorted
-                    if arguments.get('check', False) and incorrectly_sorted:
-                        wrong_sorted_files = True
-                    if sort_attempt.skipped:
-                        num_skipped += 1
-                except IOError as e:
-                    print("WARNING: Unable to parse file {0} due to {1}".format(file_name, e))
+            except IOError as e:
+                print("WARNING: Unable to parse file {0} due to {1}".format(file_name, e))
         if wrong_sorted_files:
             exit(1)
 
diff --git a/isort/natural.py b/isort/natural.py
index c02b42c3..aac8c4a3 100644
--- a/isort/natural.py
+++ b/isort/natural.py
@@ -8,7 +8,7 @@ usage:
 Copyright (C) 2013  Timothy Edmund Crosley
 
 Implementation originally from @HappyLeapSecond stack overflow user in response to:
-   https://stackoverflow.com/questions/5967500/how-to-correctly-sort-a-string-with-a-number-inside
+   http://stackoverflow.com/questions/5967500/how-to-correctly-sort-a-string-with-a-number-inside
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 documentation files (the "Software"), to deal in the Software without restriction, including without limitation
diff --git a/isort/pie_slice.py b/isort/pie_slice.py
index 178cf33e..bfb341a7 100644
--- a/isort/pie_slice.py
+++ b/isort/pie_slice.py
@@ -21,8 +21,11 @@ OTHER DEALINGS IN THE SOFTWARE.
 """
 from __future__ import absolute_import
 
+import abc
 import collections
+import functools
 import sys
+from numbers import Integral
 
 __version__ = "1.1.0"
 
@@ -44,7 +47,7 @@ native_object = object
 
 common = ['native_dict', 'native_round', 'native_filter', 'native_map', 'native_range', 'native_str', 'native_chr',
           'native_input', 'PY2', 'PY3', 'u', 'itemsview', 'valuesview', 'keysview', 'execute', 'integer_types',
-          'native_next', 'native_object', 'with_metaclass', 'lru_cache']
+          'native_next', 'native_object', 'with_metaclass', 'OrderedDict', 'lru_cache']
 
 
 def with_metaclass(meta, *bases):
@@ -82,12 +85,32 @@ def unmodified_isinstance(*bases):
 
     """
     class UnmodifiedIsInstance(type):
-        @classmethod
-        def __instancecheck__(cls, instance):
-            if cls.__name__ in (str(base.__name__) for base in bases):
-                return isinstance(instance, bases)
+        if sys.version_info[0] == 2 and sys.version_info[1] <= 6:
+
+            @classmethod
+            def __instancecheck__(cls, instance):
+                if cls.__name__ in (str(base.__name__) for base in bases):
+                    return isinstance(instance, bases)
+
+                subclass = getattr(instance, '__class__', None)
+                subtype = type(instance)
+                instance_type = getattr(abc, '_InstanceType', None)
+                if not instance_type:
+                    class test_object:
+                        pass
+                    instance_type = type(test_object)
+                if subtype is instance_type:
+                    subtype = subclass
+                if subtype is subclass or subclass is None:
+                    return cls.__subclasscheck__(subtype)
+                return (cls.__subclasscheck__(subclass) or cls.__subclasscheck__(subtype))
+        else:
+            @classmethod
+            def __instancecheck__(cls, instance):
+                if cls.__name__ in (str(base.__name__) for base in bases):
+                    return isinstance(instance, bases)
 
-            return type.__instancecheck__(cls, instance)
+                return type.__instancecheck__(cls, instance)
 
     return with_metaclass(UnmodifiedIsInstance, *bases)
 
@@ -125,11 +148,12 @@ if PY3:
 
     __all__ = common + ['urllib']
 else:
-    from itertools import ifilter as filter  # noqa: F401
-    from itertools import imap as map  # noqa: F401
-    from itertools import izip as zip  # noqa: F401
+    from itertools import ifilter as filter
+    from itertools import imap as map
+    from itertools import izip as zip
     from decimal import Decimal, ROUND_HALF_EVEN
 
+    import codecs
     str = unicode
     chr = unichr
     input = raw_input
@@ -257,10 +281,28 @@ else:
             dct['__str__'] = lambda self: self.__unicode__().encode('utf-8')
             return type.__new__(cls, name, bases, dct)
 
-        def __instancecheck__(cls, instance):
-            if cls.__name__ == "object":
-                return isinstance(instance, native_object)
-            return type.__instancecheck__(cls, instance)
+        if sys.version_info[1] <= 6:
+            def __instancecheck__(cls, instance):
+                if cls.__name__ == "object":
+                    return isinstance(instance, native_object)
+
+                subclass = getattr(instance, '__class__', None)
+                subtype = type(instance)
+                instance_type = getattr(abc, '_InstanceType', None)
+                if not instance_type:
+                    class test_object:
+                        pass
+                    instance_type = type(test_object)
+                if subtype is instance_type:
+                    subtype = subclass
+                if subtype is subclass or subclass is None:
+                    return cls.__subclasscheck__(subtype)
+                return (cls.__subclasscheck__(subclass) or cls.__subclasscheck__(subtype))
+        else:
+            def __instancecheck__(cls, instance):
+                if cls.__name__ == "object":
+                    return isinstance(instance, native_object)
+                return type.__instancecheck__(cls, instance)
 
     class object(with_metaclass(FixStr, object)):
         pass
@@ -268,6 +310,138 @@ else:
     __all__ = common + ['round', 'dict', 'apply', 'cmp', 'coerce', 'execfile', 'raw_input', 'unpacks', 'str', 'chr',
                         'input', 'range', 'filter', 'map', 'zip', 'object']
 
+if sys.version_info[0] == 2 and sys.version_info[1] < 7:
+    # OrderedDict
+    # Copyright (c) 2009 Raymond Hettinger
+    #
+    # Permission is hereby granted, free of charge, to any person
+    # obtaining a copy of this software and associated documentation files
+    # (the "Software"), to deal in the Software without restriction,
+    # including without limitation the rights to use, copy, modify, merge,
+    # publish, distribute, sublicense, and/or sell copies of the Software,
+    # and to permit persons to whom the Software is furnished to do so,
+    # subject to the following conditions:
+    #
+    #     The above copyright notice and this permission notice shall be
+    #     included in all copies or substantial portions of the Software.
+    #
+    #     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    #     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+    #     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+    #     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+    #     HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+    #     WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    #     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+    #     OTHER DEALINGS IN THE SOFTWARE.
+
+    from UserDict import DictMixin
+
+    class OrderedDict(dict, DictMixin):
+
+        def __init__(self, *args, **kwds):
+            if len(args) > 1:
+                raise TypeError('expected at most 1 arguments, got %d' % len(args))
+            try:
+                self.__end
+            except AttributeError:
+                self.clear()
+            self.update(*args, **kwds)
+
+        def clear(self):
+            self.__end = end = []
+            end += [None, end, end]         # sentinel node for doubly linked list
+            self.__map = {}                 # key --> [key, prev, next]
+            dict.clear(self)
+
+        def __setitem__(self, key, value):
+            if key not in self:
+                end = self.__end
+                curr = end[1]
+                curr[2] = end[1] = self.__map[key] = [key, curr, end]
+            dict.__setitem__(self, key, value)
+
+        def __delitem__(self, key):
+            dict.__delitem__(self, key)
+            key, prev, next = self.__map.pop(key)
+            prev[2] = next
+            next[1] = prev
+
+        def __iter__(self):
+            end = self.__end
+            curr = end[2]
+            while curr is not end:
+                yield curr[0]
+                curr = curr[2]
+
+        def __reversed__(self):
+            end = self.__end
+            curr = end[1]
+            while curr is not end:
+                yield curr[0]
+                curr = curr[1]
+
+        def popitem(self, last=True):
+            if not self:
+                raise KeyError('dictionary is empty')
+            if last:
+                key = reversed(self).next()
+            else:
+                key = iter(self).next()
+            value = self.pop(key)
+            return key, value
+
+        def __reduce__(self):
+            items = [[k, self[k]] for k in self]
+            tmp = self.__map, self.__end
+            del self.__map, self.__end
+            inst_dict = vars(self).copy()
+            self.__map, self.__end = tmp
+            if inst_dict:
+                return (self.__class__, (items,), inst_dict)
+            return self.__class__, (items,)
+
+        def keys(self):
+            return list(self)
+
+        setdefault = DictMixin.setdefault
+        update = DictMixin.update
+        pop = DictMixin.pop
+        values = DictMixin.values
+        items = DictMixin.items
+        iterkeys = DictMixin.iterkeys
+        itervalues = DictMixin.itervalues
+        iteritems = DictMixin.iteritems
+
+        def __repr__(self):
+            if not self:
+                return '%s()' % (self.__class__.__name__,)
+            return '%s(%r)' % (self.__class__.__name__, self.items())
+
+        def copy(self):
+            return self.__class__(self)
+
+        @classmethod
+        def fromkeys(cls, iterable, value=None):
+            d = cls()
+            for key in iterable:
+                d[key] = value
+            return d
+
+        def __eq__(self, other):
+            if isinstance(other, OrderedDict):
+                if len(self) != len(other):
+                    return False
+                for p, q in zip(self.items(), other.items()):
+                    if p != q:
+                        return False
+                return True
+            return dict.__eq__(self, other)
+
+        def __ne__(self, other):
+            return not self == other
+else:
+    from collections import OrderedDict
+
 
 if sys.version_info < (3, 2):
     try:
@@ -277,8 +451,6 @@ if sys.version_info < (3, 2):
 
     from functools import wraps
 
-    _CacheInfo = collections.namedtuple("CacheInfo", "hits misses maxsize currsize")
-
     def lru_cache(maxsize=100):
         """Least-recently-used cache decorator.
         Taking from: https://github.com/MiCHiLU/python-functools32/blob/master/functools32/functools32.py
@@ -289,7 +461,7 @@ if sys.version_info < (3, 2):
         View the cache statistics named tuple (hits, misses, maxsize, currsize) with
         f.cache_info().  Clear the cache and statistics with f.cache_clear().
         Access the underlying function with f.__wrapped__.
-        See: https://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used
+        See:  http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used
 
         """
         def decorating_function(user_function, tuple=tuple, sorted=sorted, len=len, KeyError=KeyError):
@@ -298,7 +470,7 @@ if sys.version_info < (3, 2):
             lock = Lock()
 
             if maxsize is None:
-                CACHE = {}
+                CACHE = dict()
 
                 @wraps(user_function)
                 def wrapper(*args, **kwds):
@@ -316,7 +488,7 @@ if sys.version_info < (3, 2):
                     misses[0] += 1
                     return result
             else:
-                CACHE = collections.OrderedDict()
+                CACHE = OrderedDict()
 
                 @wraps(user_function)
                 def wrapper(*args, **kwds):
@@ -356,7 +528,7 @@ if sys.version_info < (3, 2):
         return decorating_function
 
 else:
-    from functools import lru_cache  # noqa: F401
+    from functools import lru_cache
 
 
 class OrderedSet(collections.MutableSet):
diff --git a/isort/settings.py b/isort/settings.py
index c2b72455..15cdb210 100644
--- a/isort/settings.py
+++ b/isort/settings.py
@@ -25,10 +25,8 @@ OTHER DEALINGS IN THE SOFTWARE.
 from __future__ import absolute_import, division, print_function, unicode_literals
 
 import fnmatch
-import io
 import os
 import posixpath
-import sys
 from collections import namedtuple
 
 from .pie_slice import itemsview, lru_cache, native_str
@@ -38,7 +36,7 @@ try:
 except ImportError:
     import ConfigParser as configparser
 
-MAX_CONFIG_SEARCH_DEPTH = 25  # The number of parent directories isort will look for a config file within
+MAX_CONFIG_SEARCH_DEPTH = 25 # The number of parent directories isort will look for a config file within
 DEFAULT_SECTIONS = ('FUTURE', 'STDLIB', 'THIRDPARTY', 'FIRSTPARTY', 'LOCALFOLDER')
 
 WrapModes = ('GRID', 'VERTICAL', 'HANGING_INDENT', 'VERTICAL_HANGING_INDENT', 'VERTICAL_GRID', 'VERTICAL_GRID_GROUPED', 'NOQA')
@@ -50,7 +48,6 @@ default = {'force_to_top': [],
            'skip_glob': [],
            'line_length': 79,
            'wrap_length': 0,
-           'line_ending': None,
            'sections': DEFAULT_SECTIONS,
            'no_sections': False,
            'known_future_library': ['__future__'],
@@ -104,7 +101,6 @@ default = {'force_to_top': [],
            'multi_line_output': WrapModes.GRID,
            'forced_separate': [],
            'indent': ' ' * 4,
-           'comment_prefix': '  #',
            'length_sort': False,
            'add_imports': [],
            'remove_imports': [],
@@ -134,8 +130,7 @@ default = {'force_to_top': [],
            'force_grid_wrap': 0,
            'force_sort_within_sections': False,
            'show_diff': False,
-           'ignore_whitespace': False,
-           'no_lines_before': []}
+           'ignore_whitespace': False}
 
 
 @lru_cache()
@@ -219,7 +214,7 @@ def _as_list(value):
 
 @lru_cache()
 def _get_config_data(file_path, sections):
-    with io.open(file_path, 'r') as config_file:
+    with open(file_path, 'rU') as config_file:
         if file_path.endswith('.editorconfig'):
             line = '\n'
             last_position = config_file.tell()
@@ -230,14 +225,9 @@ def _get_config_data(file_path, sections):
                     break
                 last_position = config_file.tell()
 
-        if sys.version_info >= (3, 2):
-            config = configparser.ConfigParser()
-            config.read_file(config_file)
-        else:
-            config = configparser.SafeConfigParser()
-            config.readfp(config_file)
-
-        settings = {}
+        config = configparser.SafeConfigParser()
+        config.readfp(config_file)
+        settings = dict()
         for section in sections:
             if config.has_section(section):
                 settings.update(dict(config.items(section)))
diff --git a/setup.cfg b/setup.cfg
index 96518d7c..85548bf5 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -2,14 +2,5 @@
 universal = 1
 
 [flake8]
+ignore = F401,F403,E502,E123,E127,E128,E303,E713,E111,E241,E302,E121,E261,W391
 max-line-length = 160
-ignore =
-    # The default ignore list:
-    E121,E123,E126,E226,E24,E704,
-    # Our additions:
-    # E127: continuation line over-indented for visual indent
-    # E128: continuation line under-indented for visual indent
-    E127,E128
-
-[metadata]
-license_file = LICENSE
diff --git a/setup.py b/setup.py
index 25c6e279..59008570 100755
--- a/setup.py
+++ b/setup.py
@@ -54,7 +54,6 @@ setup(name='isort',
         'pylama.linter': ['isort = isort.pylama_isort:Linter'],
       },
       packages=['isort'],
-      install_requires=['futures; python_version == "2.7"'],
       cmdclass={'test': PyTest},
       keywords='Refactor, Python, Python2, Python3, Refactoring, Imports, Sort, Clean',
       classifiers=['Development Status :: 6 - Mature',
@@ -64,8 +63,10 @@ setup(name='isort',
                    'License :: OSI Approved :: MIT License',
                    'Programming Language :: Python',
                    'Programming Language :: Python :: 2',
+                   'Programming Language :: Python :: 2.6',
                    'Programming Language :: Python :: 2.7',
                    'Programming Language :: Python :: 3',
+                   'Programming Language :: Python :: 3.3',
                    'Programming Language :: Python :: 3.4',
                    'Programming Language :: Python :: 3.5',
                    'Programming Language :: Python :: 3.6',
diff --git a/tox.ini b/tox.ini
index 943b3568..1ee185be 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,17 +1,22 @@
+# Tox (http://tox.testrun.org/) is a tool for running tests
+# in multiple virtualenvs. This configuration file will run the
+# test suite on all supported python versions. To use it, "pip install tox"
+# and then run "tox" from this directory.
+
 [tox]
 envlist =
     isort-check,
-    py27, py34, py35, py36, pypy,
-    lint
+    py26, py27, py33, py34, py35, py36, pypy
 
 [testenv]
-deps = pytest
-commands = py.test test_isort.py {posargs}
+commands =
+    py.test test_isort.py {posargs}
+deps =
+    six
+    pytest
 
 [testenv:isort-check]
-commands = python setup.py isort
-
-[testenv:lint]
-basepython = python2.7
-deps = flake8==3.3.0
-commands = flake8
+commands =
+    python setup.py isort
+deps =
+    six


Test patch:
diff --git a/runtests.py b/runtests.py
index b699f5e7..4109bde0 100644
--- a/runtests.py
+++ b/runtests.py
@@ -1,9 +1,5 @@
 #!/usr/bin/env python
 
-# Skip linting this file until it's cleaned up:
-# https://github.com/timothycrosley/isort/issues/554
-# flake8: noqa
-
 sources = """
 eNrcvWmXG1l2INYzY1tjeGYk2ePtg32iQVGIKCKDSXZpyylUqVRFqqmuYvFwUVPOSoGRQGRmNJER
 YESAiVSrz/Ev8Dn+Bf4p/mu+29tfAMjqKo3PlNTMTOC9+7b77rv7/T//9e8+/Cx980fr23y+ai7z
diff --git a/test_isort.py b/test_isort.py
index de4f8c9c..d49a83e7 100644
--- a/test_isort.py
+++ b/test_isort.py
@@ -30,6 +30,7 @@ import sys
 import tempfile
 
 from isort.isort import SortImports, exists_case_sensitive
+from isort.pie_slice import *
 from isort.settings import WrapModes
 
 SHORT_IMPORT = "from third_party import lib1, lib2, lib3, lib4"
@@ -362,7 +363,6 @@ def test_qa_comment_case():
     test_output = SortImports(file_contents=test_input, line_length=40, multi_line_output=WrapModes.NOQA).output
     assert test_output == "import veryveryveryveryveryveryveryveryveryveryvery  # NOQA\n"
 
-
 def test_length_sort():
     """Test setting isort to sort on length instead of alphabetically."""
     test_input = ("import medium_sizeeeeeeeeeeeeee\n"
@@ -506,15 +506,6 @@ def test_skip_with_file_name():
     assert skipped
 
 
-def test_skip_within_file():
-    """Ensure skipping a whole file works."""
-    test_input = ("# isort:skip_file\n"
-                  "import django\n"
-                  "import myproject\n")
-
-    assert SortImports(file_contents=test_input, known_third_party=['django']).skipped
-
-
 def test_force_to_top():
     """Ensure forcing a single import to the top of its category works as expected."""
     test_input = ("import lib6\n"
@@ -772,12 +763,10 @@ def test_thirdy_party_overrides_standard_section():
     """Test to ensure changing the default section works as expected."""
     test_input = ("import sys\n"
                   "import os\n"
-                  "import this\n"
                   "import profile.test\n")
     test_output = SortImports(file_contents=test_input, known_third_party=['profile']).output
     assert test_output == ("import os\n"
                            "import sys\n"
-                           "import this\n"
                            "\n"
                            "import profile.test\n")
 
@@ -903,7 +892,7 @@ def test_atomic_mode():
                                                                           "from b import c, d\n")
 
     # with syntax error content is not changed
-    test_input += "while True print 'Hello world'"  # blatant syntax error
+    test_input += "while True print 'Hello world'" # blatant syntax error
     assert SortImports(file_contents=test_input, atomic=True).output == test_input
 
 
@@ -1010,6 +999,19 @@ def test_smart_lines_after_import_section():
                                                             "def my_function():\n"
                                                             "    pass\n")
 
+    # Ensure logic doesn't incorrectly skip over assignments to multi-line strings
+    test_input = ("from a import b\n"
+                  'X = """test\n'
+                  '"""\n'
+                  "def my_function():\n"
+                  "    pass\n")
+    assert SortImports(file_contents=test_input).output == ("from a import b\n"
+                                                            "\n"
+                                                            'X = """test\n'
+                                                            '"""\n'
+                                                            "def my_function():\n"
+                                                            "    pass\n")
+
 
 def test_settings_combine_instead_of_overwrite():
     """Test to ensure settings combine logically, instead of fully overwriting."""
@@ -1017,7 +1019,7 @@ def test_settings_combine_instead_of_overwrite():
            set(SortImports().config['known_standard_library'] + ['not_std_library'])
 
     assert set(SortImports(not_known_standard_library=['thread']).config['known_standard_library']) == \
-           {item for item in SortImports().config['known_standard_library'] if item != 'thread'}
+           set(item for item in SortImports().config['known_standard_library'] if item != 'thread')
 
 
 def test_combined_from_and_as_imports():
@@ -1034,8 +1036,8 @@ def test_as_imports_with_line_length():
                   "from translate.storage.placeables import general, parse as rich_parse\n")
     assert SortImports(file_contents=test_input, combine_as_imports=False, line_length=40).output == \
                   ("from translate.storage import \\\n    base as storage_base\n"
-                   "from translate.storage.placeables import \\\n    general\n"
-                   "from translate.storage.placeables import \\\n    parse as rich_parse\n")
+                   "from translate.storage.placeables import \\\n    parse as rich_parse\n"
+                   "from translate.storage.placeables import \\\n    general\n")
 
 
 def test_keep_comments():
@@ -1351,6 +1353,7 @@ def test_place_comments():
     assert test_output == expected_output
 
 
+
 def test_placement_control():
     """Ensure that most specific placement control match wins"""
     test_input = ("import os\n"
@@ -1635,7 +1638,7 @@ def test_comment_at_top_of_file():
 
 
 def test_alphabetic_sorting():
-    """Test to ensure isort correctly handles single line imports"""
+    """Test to ensure isort correctly handles top of file comments"""
     test_input = ("import unittest\n"
                   "\n"
                   "import ABC\n"
@@ -1803,7 +1806,6 @@ def test_lines_between_sections():
     assert SortImports(file_contents=test_input, lines_between_sections=2).output == ('import os\n\n\n'
                                                                                       'from bar import baz\n')
 
-
 def test_forced_sepatate_globs():
     """Test to ensure that forced_separate glob matches lines"""
     test_input = ('import os\n'
@@ -2039,7 +2041,6 @@ def test_import_line_mangles_issues_439():
     """Test to ensure comment on import with parens doesn't cause issues"""
     test_input = ('import a  # () import\n'
                   'from b import b\n')
-    assert SortImports(file_contents=test_input).output == test_input
 
 
 def test_alias_using_paren_issue_466():
@@ -2047,14 +2048,15 @@ def test_alias_using_paren_issue_466():
     test_input = 'from django.db.backends.mysql.base import DatabaseWrapper as MySQLDatabaseWrapper\n'
     expected_output = ('from django.db.backends.mysql.base import (\n'
                        '    DatabaseWrapper as MySQLDatabaseWrapper)\n')
-    assert SortImports(file_contents=test_input, line_length=50, use_parentheses=True).output == expected_output
+    assert  SortImports(file_contents=test_input, line_length=50, use_parentheses=True).output == expected_output
+
 
     test_input = 'from django.db.backends.mysql.base import DatabaseWrapper as MySQLDatabaseWrapper\n'
     expected_output = ('from django.db.backends.mysql.base import (\n'
                        '    DatabaseWrapper as MySQLDatabaseWrapper\n'
                        ')\n')
-    assert SortImports(file_contents=test_input, line_length=50, multi_line_output=5,
-                       use_parentheses=True).output == expected_output
+    assert  SortImports(file_contents=test_input, line_length=50, multi_line_output=5,
+                        use_parentheses=True).output == expected_output
 
 
 def test_strict_whitespace_by_default(capsys):
@@ -2079,8 +2081,8 @@ def test_import_wraps_with_comment_issue_471():
                   ' -- long string of comments which wrap over')
     expected_output = ('from very_long_module_name import (\n'
                        '    SuperLongClassName)  # @UnusedImport -- long string of comments which wrap over\n')
-    assert SortImports(file_contents=test_input, line_length=50, multi_line_output=1,
-                       use_parentheses=True).output == expected_output
+    assert  SortImports(file_contents=test_input, line_length=50, multi_line_output=1,
+                        use_parentheses=True).output == expected_output
 
 
 def test_import_case_produces_inconsistent_results_issue_472():
@@ -2149,92 +2151,8 @@ def test_future_below_encoding_issue_545():
     assert SortImports(file_contents=test_input).output == expected_output
 
 
-def test_no_extra_lines_issue_557():
-    """Test to ensure no extra lines are prepended"""
-    test_input = ('import os\n'
-                  '\n'
-                  'from scrapy.core.downloader.handlers.http import HttpDownloadHandler, HTTPDownloadHandler\n')
-    expected_output = ('import os\n'
-                       'from scrapy.core.downloader.handlers.http import HttpDownloadHandler, HTTPDownloadHandler\n')
-    assert SortImports(file_contents=test_input, force_alphabetical_sort=True,
-                       force_sort_within_sections=True).output == expected_output
-
-
 def test_long_import_wrap_support_with_mode_2():
     """Test to ensure mode 2 still allows wrapped imports with slash"""
     test_input = ('from foobar.foobar.foobar.foobar import \\\n'
                   '    an_even_longer_function_name_over_80_characters\n')
     assert SortImports(file_contents=test_input, multi_line_output=2, line_length=80).output == test_input
-
-
-def test_pylint_comments_incorrectly_wrapped_issue_571():
-    """Test to ensure pylint comments don't get wrapped"""
-    test_input = ('from PyQt5.QtCore import QRegExp  # @UnresolvedImport pylint: disable=import-error,'
-                  'useless-suppression\n')
-    expected_output = ('from PyQt5.QtCore import \\\n'
-                       '    QRegExp  # @UnresolvedImport pylint: disable=import-error,useless-suppression\n')
-    assert SortImports(file_contents=test_input, line_length=60).output == expected_output
-
-
-def test_ensure_async_methods_work_issue_537():
-    """Test to ensure async methods are correctly identified"""
-    test_input = ('from myapp import myfunction\n'
-                  '\n'
-                  '\n'
-                  'async def test_myfunction(test_client, app):\n'
-                  '    a = await myfunction(test_client, app)\n')
-    assert SortImports(file_contents=test_input).output == test_input
-
-
-def test_ensure_as_imports_sort_correctly_within_from_imports_issue_590():
-    """Test to ensure combination from and as import statements are sorted correct"""
-    test_input = ('from os import defpath\n'
-                  'from os import pathsep as separator\n')
-    assert SortImports(file_contents=test_input, force_sort_within_sections=True).output == test_input
-
-    test_input = ('from os import defpath\n'
-                  'from os import pathsep as separator\n')
-    assert SortImports(file_contents=test_input).output == test_input
-
-    test_input = ('from os import defpath\n'
-                  'from os import pathsep as separator\n')
-    assert SortImports(file_contents=test_input, force_single_line=True).output == test_input
-
-
-def test_ensure_line_endings_are_preserved_issue_493():
-    """Test to ensure line endings are not converted"""
-    test_input = ('from os import defpath\r\n'
-                  'from os import pathsep as separator\r\n')
-    assert SortImports(file_contents=test_input).output == test_input
-    test_input = ('from os import defpath\r'
-                  'from os import pathsep as separator\r')
-    assert SortImports(file_contents=test_input).output == test_input
-    test_input = ('from os import defpath\n'
-                  'from os import pathsep as separator\n')
-    assert SortImports(file_contents=test_input).output == test_input
-
-
-def test_not_splitted_sections():
-    whiteline = '\n'
-    stdlib_section = 'import unittest\n'
-    firstparty_section = 'from app.pkg1 import mdl1\n'
-    local_section = 'from .pkg2 import mdl2\n'
-    statement = 'foo = bar\n'
-    test_input = (
-        stdlib_section + whiteline + firstparty_section + whiteline +
-        local_section + whiteline + statement
-    )
-
-    assert SortImports(file_contents=test_input).output == test_input
-    assert SortImports(file_contents=test_input, no_lines_before=['LOCALFOLDER']).output == \
-           (
-               stdlib_section + whiteline + firstparty_section + local_section +
-               whiteline + statement
-           )
-    assert SortImports(file_contents=test_input, no_lines_before=['FIRSTPARTY']).output == \
-           (
-               stdlib_section + firstparty_section + whiteline + local_section +
-               whiteline + statement
-           )
-    assert SortImports(file_contents=test_input, no_lines_before=['FIRSTPARTY', 'LOCALFOLDER']).output == \
-           (stdlib_section + firstparty_section + local_section + whiteline + statement)