summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimothy Crosley <timothy.crosley@gmail.com>2015-02-05 17:07:59 -0500
committerTimothy Crosley <timothy.crosley@gmail.com>2015-02-05 17:07:59 -0500
commitbeede2435eb1b19e44e9b2010ea89ef1faa31efb (patch)
treefba7f3f5bb2eab5318f1f4a025c977c37a8f02cd
parenta550957f4305ac2ea47a85984898dcaf28d256ca (diff)
parent0a6dbb5a4cd8d235b5a893a2d9e1ddb79fd7124b (diff)
downloadisort-beede2435eb1b19e44e9b2010ea89ef1faa31efb.tar.gz
Merge branch 'release/3.9.5'3.9.5
-rw-r--r--.undertake4
-rw-r--r--ACKNOWLEDGEMENTS.md12
-rw-r--r--README.md31
-rw-r--r--isort/__init__.py2
-rw-r--r--isort/hooks.py82
-rw-r--r--isort/isort.py258
-rwxr-xr-xisort/main.py8
-rw-r--r--isort/settings.py3
-rwxr-xr-xsetup.py4
-rw-r--r--test_isort.py151
10 files changed, 421 insertions, 134 deletions
diff --git a/.undertake b/.undertake
new file mode 100644
index 00000000..c43281fb
--- /dev/null
+++ b/.undertake
@@ -0,0 +1,4 @@
+name=Add isort settings
+color=#3C79AB
+font_color=white
+format=instantly \ No newline at end of file
diff --git a/ACKNOWLEDGEMENTS.md b/ACKNOWLEDGEMENTS.md
index 51a96dc6..56fbc616 100644
--- a/ACKNOWLEDGEMENTS.md
+++ b/ACKNOWLEDGEMENTS.md
@@ -38,6 +38,18 @@ Code Contributors
- Elliott Sales de Andrade (@qulogic)
- Kasper Jacobsen (@dinoshauer)
- Sebastian Pipping (@hartwork)
+- Helen Sherwood-Taylor (@helenst)
+- Mocker (@Zuckonit)
+- Tim Graham (@timgraham)
+- Adam (@NorthIsUp)
+
+Documenters
+===================
+- Reinout van Rees (@reinout)
+- Helen Sherwood-Taylor (@helenst)
+- Elliott Sales de Andrade (@QuLogic)
+- Brian Peiris (@brianpeiris)
+- Tim Graham (@timgraham)
--------------------------------------------
diff --git a/README.md b/README.md
index 7a5f4ef7..f055af35 100644
--- a/README.md
+++ b/README.md
@@ -255,7 +255,17 @@ own line
Note: to change the how constant indents appear - simply change the indent property with the following accepted formats:
* Number of spaces you would like. For example: 4 would cause standard 4 space indentation.
* Tab
-* A verbatim string with quotes around it. For example: " " is equivalent to 4
+* A verbatim string with quotes around it.
+
+For example:
+
+ " "
+
+is equivalent to 4
+
+For the import styles that use parentheses, you can control whether or not to
+include a trailing comma after the last import with the include_trailing_comma
+option (defaults to false).
Intelligently Balanced Multi-line Imports
======================
@@ -391,6 +401,25 @@ https://gist.github.com/acdha/8717683
Which can help to ensure a certain level of code quality throughout a project.
+
+Git hook
+========
+
+isort provides a hook function that can be integrated into your Git pre-commit script to check
+Python code before committing.
+
+To cause the commit to fail if there are isort errors (strict mode), include the following in
+`.git/hooks/pre-commit`:
+
+ from isort.hooks import git_hook
+
+ if __name__ == '__main__':
+ sys.exit(git_hook(strict=True))
+
+If you just want to display warnings, but allow the commit to happen anyway, call git_hook without
+the `strict` parameter.
+
+
Why isort?
======================
diff --git a/isort/__init__.py b/isort/__init__.py
index 377a8634..585c81a0 100644
--- a/isort/__init__.py
+++ b/isort/__init__.py
@@ -25,4 +25,4 @@ from __future__ import absolute_import, division, print_function, unicode_litera
from . import settings
from .isort import SECTION_NAMES, SECTIONS, SortImports
-__version__ = "3.9.4"
+__version__ = "3.9.5"
diff --git a/isort/hooks.py b/isort/hooks.py
new file mode 100644
index 00000000..15b6d408
--- /dev/null
+++ b/isort/hooks.py
@@ -0,0 +1,82 @@
+"""isort.py.
+
+Defines a git hook to allow pre-commit warnings and errors about import order.
+
+usage:
+ exit_code = git_hook(strict=True)
+
+Copyright (C) 2015 Helen Sherwood-Taylor
+
+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.
+
+"""
+import subprocess
+
+from isort import SortImports
+
+
+def get_output(command):
+ """
+ Run a command and return raw output
+
+ :param str command: the command to run
+ :returns: the stdout output of the command
+ """
+ return subprocess.check_output(command.split())
+
+
+def get_lines(command):
+ """
+ Run a command and return lines of output
+
+ :param str command: the command to run
+ :returns: list of whitespace-stripped lines output by command
+ """
+ stdout = get_output(command)
+ return [line.strip().decode('utf-8') for line in stdout.splitlines()]
+
+
+def git_hook(strict=False):
+ """
+ Git pre-commit hook to check staged files for isort errors
+
+ :param bool strict - if True, return number of errors on exit,
+ causing the hook to fail. If False, return zero so it will
+ just act as a warning.
+
+ :return number of errors if in strict mode, 0 otherwise.
+ """
+
+ # Get list of files modified and staged
+ diff_cmd = "git diff-index --cached --name-only --diff-filter=ACMRTUXB HEAD"
+ files_modified = get_lines(diff_cmd)
+
+ errors = 0
+ for filename in files_modified:
+ if filename.endswith('.py'):
+ # Get the staged contents of the file
+ staged_cmd = "git show :%s" % filename
+ staged_contents = get_output(staged_cmd)
+
+ sort = SortImports(
+ file_path=filename,
+ file_contents=staged_contents.decode(),
+ check=True
+ )
+
+ if sort.incorrectly_sorted:
+ errors += 1
+
+ return errors if strict else 0
diff --git a/isort/isort.py b/isort/isort.py
index 1eabe47a..f08dbc05 100644
--- a/isort/isort.py
+++ b/isort/isort.py
@@ -84,7 +84,7 @@ class SortImports(object):
file_name = file_path
self.file_path = file_path or ""
- if file_path:
+ if file_path and not file_contents:
file_path = os.path.abspath(file_path)
if self._should_skip(file_path):
if self.config['verbose']:
@@ -216,7 +216,14 @@ class SortImports(object):
if module_name_to_check in self.config[config_key]:
return placement
- for prefix in PYTHONPATH:
+ paths = PYTHONPATH
+ virtual_env = os.environ.get('VIRTUAL_ENV', None)
+ if virtual_env:
+ paths = list(paths)
+ for version in ((2, 6), (2, 7), (3, 0), (3, 1), (3, 2), (3, 3), (3, 4)):
+ paths.append("{0}/lib/python{1}.{2}/site-packages".format(virtual_env, version[0], version[1]))
+
+ for prefix in paths:
module_path = "/".join((prefix, moduleName.replace(".", "/")))
package_path = "/".join((prefix, moduleName.split(".")[0]))
if (os.path.exists(module_path + ".py") or os.path.exists(module_path + ".so") or
@@ -281,7 +288,7 @@ class SortImports(object):
if splitter in line and not line.strip().startswith(splitter):
line_parts = line.split(splitter)
next_line = []
- while (len(line) + 2) > self.config['line_length'] and line_parts:
+ while (len(line) + 2) > (self.config['wrap_length'] or self.config['line_length']) and line_parts:
next_line.append(line_parts.pop())
line = splitter.join(line_parts)
if not line:
@@ -291,112 +298,96 @@ class SortImports(object):
return line
- def _add_formatted_imports(self):
- """Adds the imports back to the file.
+ def _add_straight_imports(self, straight_modules, section, section_output):
+ for module in straight_modules:
+ if module in self.remove_imports:
+ continue
- (at the index of the first import) sorted alphabetically and split between groups
+ if module in self.as_map:
+ import_definition = "import {0} as {1}".format(module, self.as_map[module])
+ else:
+ import_definition = "import {0}".format(module)
- """
- output = []
- for section in itertools.chain(SECTIONS, self.config['forced_separate']):
- straight_modules = list(self.imports[section]['straight'])
- straight_modules = natsorted(straight_modules, key=lambda key: self._module_key(key, self.config))
- section_output = []
+ comments_above = self.comments['above']['straight'].get(module, None)
+ if comments_above:
+ section_output.append(comments_above)
+ section_output.append(self._add_comments(self.comments['straight'].get(module), import_definition))
- for module in straight_modules:
- if module in self.remove_imports:
- continue
+ def _add_from_imports(self, from_modules, section, section_output):
+ for module in from_modules:
+ if module in self.remove_imports:
+ continue
- if module in self.as_map:
- import_definition = "import {0} as {1}".format(module, self.as_map[module])
+ import_start = "from {0} import ".format(module)
+ from_imports = list(self.imports[section]['from'][module])
+ from_imports = natsorted(from_imports, key=lambda key: self._module_key(key, self.config, True))
+ if self.remove_imports:
+ from_imports = [line for line in from_imports if not "{0}.{1}".format(module, line) in
+ self.remove_imports]
+
+ 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 = self._wrap(import_start + import_definition)
+ comments = self.comments['straight'].get(submodule)
+ import_statement = self._add_comments(comments, import_statement)
+ section_output.append(import_statement)
+ from_imports.remove(from_import)
+
+ if from_imports:
+ comments = self.comments['from'].get(module)
+ if "*" in from_imports and self.config['combine_star']:
+ import_statement = self._wrap(self._add_comments(comments, "{0}*".format(import_start)))
+ elif self.config['force_single_line']:
+ import_statements = []
+ for from_import in from_imports:
+ single_import_line = self._add_comments(comments, import_start + from_import)
+ comment = self.comments['nested'].get(module, {}).get(from_import, None)
+ if comment:
+ single_import_line += "{0} {1}".format(comments and ";" or " #", comment)
+ import_statements.append(self._wrap(single_import_line))
+ comments = None
+ import_statement = "\n".join(import_statements)
else:
- import_definition = "import {0}".format(module)
-
- comments_above = self.comments['above']['straight'].get(module, None)
- if comments_above:
- section_output.append(comments_above)
- section_output.append(self._add_comments(self.comments['straight'].get(module), import_definition))
-
- from_modules = list(self.imports[section]['from'].keys())
- from_modules = natsorted(from_modules, key=lambda key: self._module_key(key, self.config))
- for module in from_modules:
- if module in self.remove_imports:
- continue
-
- import_start = "from {0} import ".format(module)
- from_imports = list(self.imports[section]['from'][module])
- from_imports = natsorted(from_imports, key=lambda key: self._module_key(key, self.config, True))
- if self.remove_imports:
- from_imports = [line for line in from_imports if not "{0}.{1}".format(module, line) in
- self.remove_imports]
-
- 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 = self._wrap(import_start + import_definition)
- comments = self.comments['straight'].get(submodule)
- import_statement = self._add_comments(comments, import_statement)
- section_output.append(import_statement)
- from_imports.remove(from_import)
-
- if from_imports:
- comments = self.comments['from'].get(module)
- if "*" in from_imports and self.config['combine_star']:
- import_statement = self._wrap(self._add_comments(comments, "{0}*".format(import_start)))
- elif self.config['force_single_line']:
- import_statements = []
- for from_import in from_imports:
+ star_import = False
+ if "*" in from_imports:
+ section_output.append(self._add_comments(comments, "{0}*".format(import_start)))
+ from_imports.remove('*')
+ star_import = True
+ comments = None
+
+ for from_import in copy.copy(from_imports):
+ comment = self.comments['nested'].get(module, {}).get(from_import, None)
+ if comment:
single_import_line = self._add_comments(comments, import_start + from_import)
- comment = self.comments['nested'].get(module, {}).get(from_import, None)
- if comment:
- single_import_line += "{0} {1}".format(comments and ";" or " #", comment)
- import_statements.append(self._wrap(single_import_line))
- comments = None
- import_statement = "\n".join(import_statements)
- else:
- star_import = False
- if "*" in from_imports:
- section_output.append(self._add_comments(comments, "{0}*".format(import_start)))
- from_imports.remove('*')
- star_import = True
+ single_import_line += "{0} {1}".format(comments and ";" or " #", comment)
+ above_comments = self.comments['above']['from'].get(module, None)
+ if above_comments:
+ section_output.extend(above_comments)
+ section_output.append(self._wrap(single_import_line))
+ from_imports.remove(from_import)
comments = None
- for from_import in copy.copy(from_imports):
- comment = self.comments['nested'].get(module, {}).get(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 " #", comment)
- above_comments = self.comments['above']['from'].get(module, None)
- if above_comments:
- section_output.extend(above_comments)
- section_output.append(self._wrap(single_import_line))
- from_imports.remove(from_import)
- comments = None
-
- if star_import:
- import_statement = import_start + (", ").join(from_imports)
- else:
- import_statement = self._add_comments(comments, import_start + (", ").join(from_imports))
- if not from_imports:
- import_statement = ""
- if len(import_statement) > self.config['line_length']:
- 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')
- ):
+ if star_import:
+ import_statement = import_start + (", ").join(from_imports)
+ else:
+ import_statement = self._add_comments(comments, import_start + (", ").join(from_imports))
+ 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()
+ 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']
+ line_length = self.config['wrap_length'] or self.config['line_length']
import_statement = formatter(import_start, copy.copy(from_imports),
dynamic_indent, indent, line_length, comments)
if self.config['balanced_wrapping']:
@@ -408,19 +399,43 @@ class SortImports(object):
minimum_length = 0
new_import_statement = import_statement
while (len(lines[-1]) < minimum_length and
- len(lines) == line_count and line_length > 10):
+ 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)
- if import_statement:
- above_comments = self.comments['above']['from'].get(module, None)
- if above_comments:
- section_output.extend(above_comments)
- section_output.append(import_statement)
+ if import_statement:
+ above_comments = self.comments['above']['from'].get(module, None)
+ if above_comments:
+ section_output.extend(above_comments)
+ section_output.append(import_statement)
+
+ def _add_formatted_imports(self):
+ """Adds the imports back to the file.
+
+ (at the index of the first import) sorted alphabetically and split between groups
+
+ """
+ output = []
+ for section in itertools.chain(SECTIONS, self.config['forced_separate']):
+ straight_modules = list(self.imports[section]['straight'])
+ straight_modules = natsorted(straight_modules, key=lambda key: self._module_key(key, self.config))
+ from_modules = list(self.imports[section]['from'].keys())
+ from_modules = natsorted(from_modules, key=lambda key: self._module_key(key, self.config))
+
+ section_output = []
+ if self.config.get('from_first', False):
+ self._add_from_imports(from_modules, section, section_output)
+ self._add_straight_imports(straight_modules, section, section_output)
+ else:
+ self._add_straight_imports(straight_modules, section, section_output)
+ self._add_from_imports(from_modules, section, section_output)
+
if section_output:
section_name = section
if section in SECTIONS:
@@ -486,11 +501,16 @@ class SortImports(object):
comments = None
else:
statement += ", " + next_import
- return statement + ")"
+ 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) + ",") + "\n" + white_space
- return "{0}({1}{2})".format(statement, first_import, (",\n" + white_space).join(imports))
+ return "{0}({1}{2}{3})".format(
+ statement,
+ first_import,
+ (",\n" + white_space).join(imports),
+ "," if self.config['include_trailing_comma'] else "",
+ )
def _output_hanging_indent(self, statement, imports, white_space, indent, line_length, comments):
statement += imports.pop(0)
@@ -505,8 +525,13 @@ class SortImports(object):
return statement
def _output_vertical_hanging_indent(self, statement, imports, white_space, indent, line_length, comments):
- return "{0}({1}\n{2}{3}\n)".format(statement, self._add_comments(comments), indent,
- (",\n" + indent).join(imports))
+ return "{0}({1}\n{2}{3}{4}\n)".format(
+ statement,
+ self._add_comments(comments),
+ indent,
+ (",\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, "(") + "\n" + indent + imports.pop(0)
@@ -516,6 +541,8 @@ class SortImports(object):
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 += ','
return statement
def _output_vertical_grid(self, statement, imports, white_space, indent, line_length, comments):
@@ -591,8 +618,13 @@ class SortImports(object):
def _strip_syntax(self, import_string):
import_string = import_string.replace("_import", "[[i]]")
- for remove_syntax in ['\\', '(', ')', ",", 'from ', 'import ']:
+ for remove_syntax in ['\\', '(', ')', ',']:
import_string = import_string.replace(remove_syntax, " ")
+ import_list = import_string.split()
+ for key in ('from', 'import'):
+ if key in import_list:
+ import_list.remove(key)
+ import_string = ' '.join(import_list)
import_string = import_string.replace("[[i]]", "_import")
return import_string
@@ -608,8 +640,8 @@ class SortImports(object):
self.import_index = self.index - 1
continue
- if "isort:imports-" in line:
- section = line.split("isort:imports-")[-1].split()[0]
+ if "isort:" + "imports-" in line:
+ section = line.split("isort:" + "imports-")[-1].split()[0]
self.place_imports[section.upper()] = []
self.import_placements[line] = section.upper()
@@ -688,7 +720,8 @@ class SortImports(object):
if comments:
self.comments['from'].setdefault(import_from, []).extend(comments)
last = self.out_lines and self.out_lines[-1].rstrip() or ""
- if last.startswith("#") and not last.endswith('"""') and not last.endswith("'''"):
+ if (len(self.out_lines) > self.import_index and last.startswith("#") and not last.endswith('"""') and not
+ last.endswith("'''")):
self.comments['above']['from'].setdefault(import_from, []).append(self.out_lines.pop(-1))
if root.get(import_from, False):
root[import_from].update(imports)
@@ -700,6 +733,7 @@ class SortImports(object):
self.comments['straight'][module] = comments
comments = None
last = self.out_lines and self.out_lines[-1].rstrip() or ""
- if last.startswith("#") and not last.endswith('"""') and not last.endswith("'''"):
+ if (len(self.out_lines) > self.import_index and last.startswith("#") and not
+ last.endswith('"""') and not last.endswith("'''")):
self.comments['above']['from'][module] = self.out_lines.pop(-1)
self.imports[self.place_module(module)][import_type].add(module)
diff --git a/isort/main.py b/isort/main.py
index d28908a4..a73ad329 100755
--- a/isort/main.py
+++ b/isort/main.py
@@ -99,13 +99,17 @@ def main():
help="Ensures the output doesn't save if the resulting file contains syntax errors.")
parser.add_argument('-cs', '--combine-star', dest='combine_star', action='store_true',
help="Ensures that if a star import is present, nothing else is imported from that namespace.")
+ parser.add_argument('-tc', '--trailing-comma', dest='trailing_comma', action='store_true',
+ help='Includes a trailing comma on multi line imports that include parentheses.')
parser.add_argument('-v', '--version', action='version', version='isort {0}'.format(__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.')
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')
+ parser.add_argument('-ff', '--from-first', dest='from_first',
+ 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.")
arguments = dict((key, value) for (key, value) in itemsview(vars(parser.parse_args())) if value)
file_names = arguments.pop('files', [])
diff --git a/isort/settings.py b/isort/settings.py
index f3aa3ae3..0b14bec6 100644
--- a/isort/settings.py
+++ b/isort/settings.py
@@ -44,6 +44,7 @@ WrapModes = namedtuple('WrapModes', WrapModes)(*range(len(WrapModes)))
default = {'force_to_top': [],
'skip': ['__init__.py', ],
'line_length': 79,
+ 'wrap_length': 0,
'known_future_library': ['__future__'],
'known_standard_library': ["abc", "anydbm", "argparse", "array", "asynchat", "asyncore", "atexit", "base64",
"BaseHTTPServer", "bisect", "bz2", "calendar", "cgitb", "cmd", "codecs",
@@ -86,6 +87,8 @@ default = {'force_to_top': [],
'lines_after_imports': -1,
'combine_as_imports': False,
'combine_star': False,
+ 'include_trailing_comma': False,
+ 'from_first': False,
'verbose': False}
diff --git a/setup.py b/setup.py
index 8d391213..ffc57d56 100755
--- a/setup.py
+++ b/setup.py
@@ -42,13 +42,13 @@ except (IOError, ImportError, OSError, RuntimeError):
readme = ''
setup(name='isort',
- version='3.9.4',
+ version='3.9.5',
description='A Python utility / library to sort Python imports.',
long_description=readme,
author='Timothy Crosley',
author_email='timothy.crosley@gmail.com',
url='https://github.com/timothycrosley/isort',
- download_url='https://github.com/timothycrosley/isort/archive/3.9.4.tar.gz',
+ download_url='https://github.com/timothycrosley/isort/archive/3.9.5.tar.gz',
license="MIT",
entry_points={
'console_scripts': [
diff --git a/test_isort.py b/test_isort.py
index 43264ce2..2aeb474c 100644
--- a/test_isort.py
+++ b/test_isort.py
@@ -3,6 +3,8 @@
Tests all major functionality of the isort library
Should be ran using py.test by simply running by.test in the isort project directory
+Copyright (C) 2013 Timothy Edmund Crosley
+
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
@@ -25,6 +27,7 @@ from pies.overrides import *
from isort.isort import SortImports
from isort.settings import WrapModes
+SHORT_IMPORT = "from third_party import lib1, lib2, lib3, lib4"
REALLY_LONG_IMPORT = ("from third_party import lib1, lib2, lib3, lib4, lib5, lib6, lib7, lib8, lib9, lib10, lib11,"
"lib12, lib13, lib14, lib15, lib16, lib17, lib18, lib20, lib21, lib22")
REALLY_LONG_IMPORT_WITH_COMMENT = ("from third_party import lib1, lib2, lib3, lib4, lib5, lib6, lib7, lib8, lib9, "
@@ -140,6 +143,30 @@ def test_line_length():
" lib21, lib22)\n")
+ test_output = SortImports(file_contents=REALLY_LONG_IMPORT, line_length=42, wrap_length=32).output
+ assert test_output == ("from third_party import (lib1,\n"
+ " lib2,\n"
+ " lib3,\n"
+ " lib4,\n"
+ " lib5,\n"
+ " lib6,\n"
+ " lib7,\n"
+ " lib8,\n"
+ " lib9,\n"
+ " lib10,\n"
+ " lib11,\n"
+ " lib12,\n"
+ " lib13,\n"
+ " lib14,\n"
+ " lib15,\n"
+ " lib16,\n"
+ " lib17,\n"
+ " lib18,\n"
+ " lib20,\n"
+ " lib21,\n"
+ " lib22)\n")
+
+
def test_output_modes():
"""Test setting isort to use various output modes works as expected"""
test_output_grid = SortImports(file_contents=REALLY_LONG_IMPORT,
@@ -935,6 +962,69 @@ def test_import_star():
"from blah import _potato\n")
assert SortImports(file_contents=test_input, combine_star=True).output == ("from blah import *\n")
+def test_include_trailing_comma():
+ """Test for the include_trailing_comma option"""
+ test_output_grid = SortImports(
+ file_contents=SHORT_IMPORT,
+ multi_line_output=WrapModes.GRID,
+ line_length=40,
+ include_trailing_comma=True,
+ ).output
+ assert test_output_grid == (
+ "from third_party import (lib1, lib2,\n"
+ " lib3, lib4,)\n"
+ )
+
+ test_output_vertical = SortImports(
+ file_contents=SHORT_IMPORT,
+ multi_line_output=WrapModes.VERTICAL,
+ line_length=40,
+ include_trailing_comma=True,
+ ).output
+ assert test_output_vertical == (
+ "from third_party import (lib1,\n"
+ " lib2,\n"
+ " lib3,\n"
+ " lib4,)\n"
+ )
+
+ test_output_vertical_indent = SortImports(
+ file_contents=SHORT_IMPORT,
+ multi_line_output=WrapModes.VERTICAL_HANGING_INDENT,
+ line_length=40,
+ include_trailing_comma=True,
+ ).output
+ assert test_output_vertical_indent == (
+ "from third_party import (\n"
+ " lib1,\n"
+ " lib2,\n"
+ " lib3,\n"
+ " lib4,\n"
+ ")\n"
+ )
+
+ test_output_vertical_grid = SortImports(
+ file_contents=SHORT_IMPORT,
+ multi_line_output=WrapModes.VERTICAL_GRID,
+ line_length=40,
+ include_trailing_comma=True,
+ ).output
+ assert test_output_vertical_grid == (
+ "from third_party import (\n"
+ " lib1, lib2, lib3, lib4,)\n"
+ )
+
+ test_output_vertical_grid_grouped = SortImports(
+ file_contents=SHORT_IMPORT,
+ multi_line_output=WrapModes.VERTICAL_GRID_GROUPED,
+ line_length=40,
+ include_trailing_comma=True,
+ ).output
+ assert test_output_vertical_grid_grouped == (
+ "from third_party import (\n"
+ " lib1, lib2, lib3, lib4,\n"
+ ")\n"
+ )
def test_similar_to_std_library():
"""Test to ensure modules that are named similarly to a standard library import don't end up clobbered"""
@@ -1117,19 +1207,48 @@ def test_sticky_comments():
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
-)
-"""
+def test_zipimport():
+ """Imports ending in "import" shouldn't be clobbered"""
+ test_input = "from zipimport import zipimport\n"
+ assert SortImports(file_contents=test_input).output == test_input
+
+
+def test_from_ending():
+ """Imports ending in "from" shouldn't be clobbered."""
+ test_input = "from foo import get_foo_from, get_foo\n"
+ expected_output = "from foo import get_foo, get_foo_from\n"
+ assert SortImports(file_contents=test_input).output == expected_output
+
+
+def test_from_first():
+ """Tests the setting from_first works correctly"""
+ test_input = "from os import path\nimport os\n"
+ assert SortImports(file_contents=test_input, from_first=True).output == test_input
+
+
+def test_top_comments():
+ """Ensure correct behavior with top comments"""
+ test_input = ("# -*- encoding: utf-8 -*-\n"
+ "# Test comment\n"
+ "#\n"
+ "from __future__ import unicode_literals\n")
+ assert SortImports(file_contents=test_input).output == test_input
+
+ test_input = ("# -*- coding: utf-8 -*-\n"
+ "from django.db import models\n"
+ "from django.utils.encoding import python_2_unicode_compatible\n")
+ assert SortImports(file_contents=test_input).output == test_input
+
+ test_input = ("# Comment\n"
+ "import sys\n")
+ assert SortImports(file_contents=test_input).output == test_input
+
+ test_input = ("# -*- coding\n"
+ "import sys\n")
+ assert SortImports(file_contents=test_input).output == test_input
+
+
+def test_consistency():
+ """Ensures consistency of handling even when dealing with non ordered-by-type imports"""
+ test_input = "from sqlalchemy.dialects.postgresql import ARRAY, array\n"
+ assert SortImports(file_contents=test_input, order_by_type=False).output == test_input