summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimothy Edmund Crosley <timothy.crosley@gmail.com>2017-04-01 16:22:11 -0700
committerGitHub <noreply@github.com>2017-04-01 16:22:11 -0700
commite70653f984e40e441098864e275f75e34bffa744 (patch)
tree03ca912c906dc258dc55b358f671bcff2308c2a2
parent7bb5b45c291bdd7ef7e7e589e77de61e3bde68e8 (diff)
parent7778d2bde51af7ff2d46fbf1f84a568e55745397 (diff)
downloadisort-e70653f984e40e441098864e275f75e34bffa744.tar.gz
Merge branch 'develop' into feature/trailing_comma_in_single_wrap
-rw-r--r--ACKNOWLEDGEMENTS.md3
-rw-r--r--CHANGELOG.md7
-rw-r--r--README.rst2
-rw-r--r--appveyor.yml12
-rw-r--r--isort/__main__.py (renamed from isort/__main__)0
-rw-r--r--isort/isort.py101
-rwxr-xr-xisort/main.py8
-rw-r--r--isort/settings.py3
-rwxr-xr-xsetup.py1
-rw-r--r--test_isort.py109
10 files changed, 206 insertions, 40 deletions
diff --git a/ACKNOWLEDGEMENTS.md b/ACKNOWLEDGEMENTS.md
index 56b26198..e59a24b1 100644
--- a/ACKNOWLEDGEMENTS.md
+++ b/ACKNOWLEDGEMENTS.md
@@ -67,7 +67,10 @@ Code Contributors
- Dan Palmer (@danpalmer)
- Andy Boot (@bootandy)
- @m7v8
+- John Vandenberg (@jayvdb)
- Adam Chainz (@adamchainz)
+- @Brightcells
+- Jonas Trappenberg (@teeberg)
Documenters
===================
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9972e04a..bdffa81e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -58,3 +58,10 @@ Changelog
### 4.2.5
- Fixed an issue that caused modules to inccorectly be matched as thirdparty when they simply had `src` in the leading path, even if they weren't withing $VIRTUALENV/src #414
+
+### 4.2.6
+- Added --enforce-whitespace option to go along with --check-only for more exact checks (issue #423)
+- Fixed imports with a tailing '\' and no space in-between getting removed (issue #425)
+
+### 4.2.7
+- Added `--virtual-env` switch command line option
diff --git a/README.rst b/README.rst
index db35bfb9..2586eb70 100644
--- a/README.rst
+++ b/README.rst
@@ -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.6 - 3.5 using `pies <https://github.com/timothycrosley/pies>`_ to achieve this without ugly hacks and/or py2to3.
+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
new file mode 100644
index 00000000..65e0d282
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,12 @@
+init:
+ - "SET PATH=C:\\Python27\\Scripts;%PATH%"
+ - "ECHO PATH=%PATH%"
+ - python --version
+
+build: off
+
+install:
+ - pip install tox
+
+test_script:
+ - tox -e isort-check,py26,py27,py33,py34,py35
diff --git a/isort/__main__ b/isort/__main__.py
index 94b1d057..94b1d057 100644
--- a/isort/__main__
+++ b/isort/__main__.py
diff --git a/isort/isort.py b/isort/isort.py
index 7864c010..869affe5 100644
--- a/isort/isort.py
+++ b/isort/isort.py
@@ -38,7 +38,6 @@ from difflib import unified_diff
from fnmatch import fnmatch
from glob import glob
from sys import path as PYTHONPATH
-from sys import stdout
from . import settings
from .natural import nsorted
@@ -162,20 +161,24 @@ class SortImports(object):
return
if check:
- if self.output.replace("\n", "").replace(" ", "") == file_contents.replace("\n", "").replace(" ", ""):
+ check_output = self.output
+ check_against = file_contents
+ if not self.config.get('enforce_white_space', False):
+ check_output = check_output.replace("\n", "").replace(" ", "")
+ check_against = check_against.replace("\n", "").replace(" ", "")
+
+ if check_output == check_against:
if self.config['verbose']:
print("SUCCESS: {0} Everything Looks Good!".format(self.file_path))
- else:
- print("ERROR: {0} Imports are incorrectly sorted.".format(self.file_path))
- self.incorrectly_sorted = True
- if show_diff or self.config.get('show_diff', False) is True:
- self._show_diff(file_contents)
- return
+ return
+
+ print("ERROR: {0} Imports are incorrectly sorted.".format(self.file_path))
+ self.incorrectly_sorted = True
if show_diff or self.config.get('show_diff', False) is True:
self._show_diff(file_contents)
elif write_to_stdout:
- stdout.write(self.output)
+ sys.stdout.write(self.output)
elif file_name:
if ask_to_apply:
if self.output == file_contents:
@@ -201,7 +204,7 @@ class SortImports(object):
if self.file_path else datetime.now()),
tofiledate=str(datetime.now())
):
- stdout.write(line)
+ sys.stdout.write(line)
@staticmethod
def _strip_top_comments(lines):
@@ -248,15 +251,20 @@ class SortImports(object):
paths += [path for path in glob('{0}/src/*'.format(virtual_env)) if os.path.isdir(path)]
virtual_env_src = '{0}/src/'.format(virtual_env)
+ # handle case-insensitive paths on windows
+ stdlib_lib_prefix = os.path.normcase(get_stdlib_path())
+
for prefix in paths:
module_path = "/".join((prefix, module_name.replace(".", "/")))
package_path = "/".join((prefix, module_name.split(".")[0]))
- if (os.path.exists(module_path + ".py") or os.path.exists(module_path + ".so") or
- (os.path.exists(package_path) and os.path.isdir(package_path))):
+ is_module = (exists_case_sensitive(module_path + ".py") or
+ exists_case_sensitive(module_path + ".so"))
+ is_package = exists_case_sensitive(package_path) and os.path.isdir(package_path)
+ if is_module or is_package:
if ('site-packages' in prefix or 'dist-packages' in prefix or
(virtual_env and virtual_env_src in prefix)):
return self.sections.THIRDPARTY
- elif 'python2' in prefix.lower() or 'python3' in prefix.lower():
+ elif os.path.normcase(prefix).startswith(stdlib_lib_prefix):
return self.sections.STDLIB
else:
return self.config['default_section']
@@ -328,11 +336,14 @@ class SortImports(object):
cont_line = self._wrap(self.config['indent'] + splitter.join(next_line).lstrip())
if self.config['use_parentheses']:
- if self.config.get('include_trailing_comma', 0):
- cont_line += ","
- if wrap_mode in (settings.WrapModes.VERTICAL_HANGING_INDENT, settings.WrapModes.VERTICAL_GRID_GROUPED,):
- cont_line += "\n"
- return "{0}{1} (\n{2})".format(line, splitter, cont_line)
+ return "{0}{1} (\n{2}{3}{4})".format(
+ line, splitter, cont_line,
+ "," if self.config['include_trailing_comma'] else "",
+ "\n" if wrap_mode in (
+ settings.WrapModes.VERTICAL_HANGING_INDENT,
+ settings.WrapModes.VERTICAL_GRID_GROUPED,
+ ) else ""
+ )
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:
@@ -422,10 +433,21 @@ class SortImports(object):
import_statement = self._add_comments(comments, import_start + (", ").join(from_imports))
if not from_imports:
import_statement = ""
- if len(from_imports) > 1 and (
- len(import_statement) > self.config['line_length']
- or self.config.get('force_grid_wrap')
- ):
+
+ do_multiline_reformat = False
+
+ if self.config.get('force_grid_wrap') and len(from_imports) > 1:
+ do_multiline_reformat = True
+
+ 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_imports) > 0
+ and self.config.get('multi_line_output', 0) not in (1, 0)):
+ do_multiline_reformat = True
+
+ if do_multiline_reformat:
output_mode = settings.WrapModes._fields[self.config.get('multi_line_output',
0)].lower()
formatter = getattr(self, "_output_" + output_mode, self._output_grid)
@@ -449,7 +471,8 @@ class SortImports(object):
new_import_statement = formatter(import_start, copy.copy(from_imports),
dynamic_indent, indent, line_length, comments)
lines = new_import_statement.split("\n")
- elif len(import_statement) > self.config['line_length']:
+
+ if not do_multiline_reformat and len(import_statement) > self.config['line_length']:
import_statement = self._wrap(import_statement)
if import_statement:
@@ -780,7 +803,7 @@ class SortImports(object):
if import_string.strip().endswith(" import") or line.strip().startswith("import "):
import_string += "\n" + line
else:
- import_string = import_string.rstrip().rstrip("\\") + line.lstrip()
+ import_string = import_string.rstrip().rstrip("\\") + " " + line.lstrip()
if import_type == "from":
import_string = import_string.replace("import(", "import (")
@@ -848,8 +871,7 @@ class SortImports(object):
and not 'isort:imports-' in last):
self.comments['above']['straight'].setdefault(module, []).insert(0,
self.out_lines.pop(-1))
- if len(self.out_lines) > max(self.import_index - 1, self._first_comment_index_end,
- 1) - 1:
+ if len(self.out_lines) > 0:
last = self.out_lines[-1].rstrip()
else:
last = ""
@@ -880,3 +902,30 @@ def coding_check(fname, default='utf-8'):
break
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.
+
+ When finding files that can be imported, it is important for the cases to match because while
+ file os.path.exists("module.py") and os.path.exists("MODULE.py") both return True on Windows, Python
+ can only import using the case of the real file.
+ """
+ result = os.path.exists(path)
+ 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 600fbad5..b5ca93d9 100755
--- a/isort/main.py
+++ b/isort/main.py
@@ -162,6 +162,8 @@ def create_parser():
help='Force sortImports to recognize a module as being part of a third party library.')
parser.add_argument('-p', '--project', dest='known_first_party', action='append',
help='Force sortImports to recognize a module as being part of the current python project.')
+ parser.add_argument('--virtual-env', dest='virtual_env',
+ help='Virtual environment to use for determining whether a package is third-party')
parser.add_argument('-m', '--multi_line', dest='multi_line_output', type=int, choices=[0, 1, 2, 3, 4, 5],
help='Multi line output (0-grid, 1-vertical, 2-hanging, 3-vert-hanging, 4-vert-grid, '
'5-vert-grid-grouped).')
@@ -181,6 +183,8 @@ def create_parser():
parser.add_argument('-c', '--check-only', action='store_true', default=False, dest="check",
help='Checks the file for unsorted / unformatted imports and prints them to the '
'command line without modifying the file.')
+ parser.add_argument('-ws', '--enforce-white-space', action='store_true', default=False, dest="enforce_white_space",
+ help='Tells isort to enforce white space difference when --check-only is being used.')
parser.add_argument('-sl', '--force-single-line-imports', dest='force_single_line', action='store_true',
help='Forces all from imports to appear on their own line')
parser.add_argument('--force_single_line_imports', dest='force_single_line', action='store_true',
@@ -243,6 +247,10 @@ def main():
print(INTRO)
return
+ if 'settings_path' in arguments:
+ sp = arguments['settings_path']
+ arguments['settings_path'] = os.path.abspath(sp) if os.path.isdir(sp) else os.path.dirname(os.path.abspath(sp))
+
file_names = arguments.pop('files', [])
if file_names == ['-']:
SortImports(file_contents=sys.stdin.read(), write_to_stdout=True, **arguments)
diff --git a/isort/settings.py b/isort/settings.py
index 26cec5bd..d17ba58a 100644
--- a/isort/settings.py
+++ b/isort/settings.py
@@ -103,7 +103,8 @@ default = {'force_to_top': [],
'force_alphabetical_sort': False,
'force_grid_wrap': False,
'force_sort_within_sections': False,
- 'show_diff': False}
+ 'show_diff': False,
+ 'enforce_white_space': False}
@lru_cache()
diff --git a/setup.py b/setup.py
index c07a2bc7..406971b4 100755
--- a/setup.py
+++ b/setup.py
@@ -45,7 +45,6 @@ setup(name='isort',
author='Timothy Crosley',
author_email='timothy.crosley@gmail.com',
url='https://github.com/timothycrosley/isort',
- download_url='https://github.com/timothycrosley/isort/archive/4.2.5.tar.gz',
license="MIT",
entry_points={
'console_scripts': [
diff --git a/test_isort.py b/test_isort.py
index b60aca24..9520fea7 100644
--- a/test_isort.py
+++ b/test_isort.py
@@ -28,7 +28,7 @@ import os
import shutil
import tempfile
-from isort.isort import SortImports
+from isort.isort import exists_case_sensitive, SortImports
from isort.pie_slice import *
from isort.settings import WrapModes
@@ -435,14 +435,48 @@ def test_custom_indent():
def test_use_parentheses():
test_input = (
- "from fooooooooooooooooooooooooo.baaaaaaaaaaaaaaaaaaarrrrrrr import \\"
+ "from fooooooooooooooooooooooooo.baaaaaaaaaaaaaaaaaaarrrrrrr import "
" my_custom_function as my_special_function"
)
test_output = SortImports(
- file_contents=test_input, known_third_party=['django'],
- line_length=79, use_parentheses=True,
+ file_contents=test_input, line_length=79, use_parentheses=True
).output
- assert '(' in test_output
+
+ assert test_output == (
+ "from fooooooooooooooooooooooooo.baaaaaaaaaaaaaaaaaaarrrrrrr import (\n"
+ " my_custom_function as my_special_function)\n"
+ )
+
+ test_output = SortImports(
+ file_contents=test_input, line_length=79, use_parentheses=True,
+ include_trailing_comma=True,
+ ).output
+
+ assert test_output == (
+ "from fooooooooooooooooooooooooo.baaaaaaaaaaaaaaaaaaarrrrrrr import (\n"
+ " my_custom_function as my_special_function,)\n"
+ )
+
+ test_output = SortImports(
+ file_contents=test_input, line_length=79, use_parentheses=True,
+ multi_line_output=WrapModes.VERTICAL_HANGING_INDENT
+ ).output
+
+ assert test_output == (
+ "from fooooooooooooooooooooooooo.baaaaaaaaaaaaaaaaaaarrrrrrr import (\n"
+ " my_custom_function as my_special_function\n)\n"
+ )
+
+ test_output = SortImports(
+ file_contents=test_input, line_length=79, use_parentheses=True,
+ multi_line_output=WrapModes.VERTICAL_GRID_GROUPED,
+ include_trailing_comma=True
+ ).output
+
+ assert test_output == (
+ "from fooooooooooooooooooooooooo.baaaaaaaaaaaaaaaaaaarrrrrrr import (\n"
+ " my_custom_function as my_special_function,\n)\n"
+ )
def test_skip():
@@ -771,6 +805,7 @@ def test_force_single_line_long_imports():
assert test_output == ("from veryveryveryveryveryvery import big\n"
"from veryveryveryveryveryvery import small # NOQA\n")
+
def test_titled_imports():
"""Tests setting custom titled/commented import sections."""
test_input = ("import sys\n"
@@ -830,6 +865,20 @@ def test_multiline_import():
assert SortImports(file_contents=test_input, **custom_configuration).output == expected_output
+def test_single_multiline():
+ """Test the case where a single import spawns multiple lines."""
+ test_input = ("from os import\\\n"
+ " getuid\n"
+ "\n"
+ "print getuid()\n")
+ output = SortImports(file_contents=test_input).output
+ assert output == (
+ "from os import getuid\n"
+ "\n"
+ "print getuid()\n"
+ )
+
+
def test_atomic_mode():
# without syntax error, everything works OK
test_input = ("from b import d, c\n"
@@ -1485,15 +1534,14 @@ def test_fcntl():
def test_import_split_is_word_boundary_aware():
"""Test to ensure that isort splits words in a boundry aware mannor"""
- test_input = ("from mycompany.model.size_value_array_import_func import ("
- " get_size_value_array_import_func_jobs,"
- ")")
+ test_input = ("from mycompany.model.size_value_array_import_func import \\\n"
+ " get_size_value_array_import_func_jobs")
test_output = SortImports(file_contents=test_input,
multi_line_output=WrapModes.VERTICAL_HANGING_INDENT,
line_length=79).output
-
- assert test_output == ("from mycompany.model.size_value_array_import_func import \\\n"
- " get_size_value_array_import_func_jobs\n")
+ assert test_output == ("from mycompany.model.size_value_array_import_func import (\n"
+ " get_size_value_array_import_func_jobs\n"
+ ")\n")
def test_other_file_encodings():
@@ -1780,6 +1828,18 @@ def test_import_by_paren_issue_375():
assert SortImports(file_contents=test_input).output == 'from .models import Bar, Foo\n'
+def test_import_by_paren_issue_460():
+ """Test to ensure isort can doesnt move comments around """
+ test_input = """
+# First comment
+# Second comment
+# third comment
+import io
+import os
+"""
+ assert SortImports(file_contents=(test_input)).output == test_input
+
+
def test_function_with_docstring():
"""Test to ensure isort can correctly sort imports when the first found content is a function with a docstring"""
add_imports = ['from __future__ import unicode_literals']
@@ -1809,3 +1869,30 @@ def test_plone_style():
options = {'force_single_line': True,
'force_alphabetical_sort': True}
assert SortImports(file_contents=test_input, **options).output == test_input
+
+
+def test_third_party_case_sensitive():
+ """Modules which match builtins by name but not on case should not be picked up on Windows."""
+ test_input = ("import thirdparty\n"
+ "import os\n"
+ "import ABC\n")
+
+ expected_output = ('import os\n'
+ '\n'
+ 'import ABC\n'
+ 'import thirdparty\n')
+ assert SortImports(file_contents=test_input).output == expected_output
+
+
+def test_exists_case_sensitive_file(tmpdir):
+ """Test exists_case_sensitive function for a file."""
+ tmpdir.join('module.py').ensure(file=1)
+ assert exists_case_sensitive(str(tmpdir.join('module.py')))
+ assert not exists_case_sensitive(str(tmpdir.join('MODULE.py')))
+
+
+def test_exists_case_sensitive_directory(tmpdir):
+ """Test exists_case_sensitive function for a directory."""
+ tmpdir.join('pkg').ensure(dir=1)
+ assert exists_case_sensitive(str(tmpdir.join('pkg')))
+ assert not exists_case_sensitive(str(tmpdir.join('PKG')))