summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimothy Crosley <timothy.crosley@gmail.com>2016-03-28 05:13:53 -0700
committerTimothy Crosley <timothy.crosley@gmail.com>2016-03-28 05:13:53 -0700
commit2282150cf22d9c44ea0cf78b6dbf3209489dbd5b (patch)
tree49584cf3289d620d062004e2060b33127956ca76
parent0338b9e6d01dccdfb7262de073b7cf1a6c8ce595 (diff)
parent36a440dbbabb6cfdd104252bc28fabb24254bbfa (diff)
downloadisort-2282150cf22d9c44ea0cf78b6dbf3209489dbd5b.tar.gz
Merge branch 'release/4.2.3'4.2.3
-rw-r--r--.env49
-rw-r--r--.gitignore6
-rw-r--r--.travis.yml7
-rw-r--r--ACKNOWLEDGEMENTS.md8
-rw-r--r--CHANGELOG.md7
-rw-r--r--README.rst7
-rw-r--r--isort/__init__.py2
-rw-r--r--isort/isort.py190
-rwxr-xr-xisort/main.py16
-rw-r--r--isort/natural.py5
-rw-r--r--isort/pie_slice.py4
-rw-r--r--isort/pylama_isort.py2
-rw-r--r--isort/settings.py17
-rw-r--r--requirements.txt2
-rwxr-xr-xsetup.py4
-rw-r--r--test_isort.py157
-rw-r--r--tox.ini4
17 files changed, 335 insertions, 152 deletions
diff --git a/.env b/.env
index d53c77e1..78c38a4d 100644
--- a/.env
+++ b/.env
@@ -12,6 +12,34 @@ fi
export PROJECT_NAME=$OPEN_PROJECT_NAME
export PROJECT_DIR="$PWD"
+if [ ! -d "venv" ]; then
+ if ! hash pyvenv 2>/dev/null; then
+ function pyvenv()
+ {
+ if hash pyvenv-3.5 2>/dev/null; then
+ pyvenv-3.5 $@
+ fi
+ if hash pyvenv-3.4 2>/dev/null; then
+ pyvenv-3.4 $@
+ fi
+ if hash pyvenv-3.3 2>/dev/null; then
+ pyvenv-3.3 $@
+ fi
+ if hash pyvenv-3.2 2>/dev/null; then
+ pyvenv-3.2 $@
+ fi
+ }
+ fi
+
+ echo "Making venv for $PROJECT_NAME"
+ pyvenv venv
+ . venv/bin/activate
+ python setup.py install
+ pip install -r requirements.txt
+fi
+
+. venv/bin/activate
+
# Let's make sure this is a hubflow enabled repo
yes | git hf init >/dev/null 2>/dev/null
@@ -20,11 +48,12 @@ alias root="cd $PROJECT_DIR"
alias project="root; cd $PROJECT_NAME"
# Commands
-alias test="root; py.test -s"
+alias test="root; py.test test_isort.py -s"
alias install="_install_project"
alias distribute="python setup.py sdist upload; python setup.py bdist_wheel upload"
alias leave="_leave_project"
+
function _install_project()
{
CURRENT_DIRECTORY="$PWD"
@@ -34,6 +63,19 @@ function _install_project()
cd $CURRENT_DIRECTORY
}
+
+function load {
+ (root
+ python setup.py install)
+}
+
+
+function interact {
+ (load
+ ipython -c "import isort" -i)
+}
+
+
function _leave_project()
{
export PROJECT_NAME=""
@@ -45,4 +87,9 @@ function _leave_project()
unalias install
unalias distribute
unalias leave
+
+ unset -f load
+ unset -f interact
+
+ deactivate
}
diff --git a/.gitignore b/.gitignore
index a4399826..24a8bb32 100644
--- a/.gitignore
+++ b/.gitignore
@@ -62,9 +62,5 @@ atlassian-ide-plugin.xml
pip-selfcheck.json
# Python3 Venv Files
-bin/
-include/
-lib/
-lib64
+venv/
pyvenv.cfg
-share/
diff --git a/.travis.yml b/.travis.yml
index 5ed06553..6be4257f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,11 +3,16 @@ env:
- TOXENV=isort-check
- TOXENV=py26
- TOXENV=py27
- - TOXENV=py32
- TOXENV=py33
- TOXENV=py34
- TOXENV=pypy
+matrix:
+ include:
+ - python: 3.5
+ env:
+ - TOXENV=py35
install:
- pip install tox
script:
- tox -e $TOXENV
+sudo: false
diff --git a/ACKNOWLEDGEMENTS.md b/ACKNOWLEDGEMENTS.md
index 25788211..9b173ec0 100644
--- a/ACKNOWLEDGEMENTS.md
+++ b/ACKNOWLEDGEMENTS.md
@@ -59,6 +59,13 @@ Code Contributors
- Ankur Dedania (@AbsoluteMSTR)
- Lee Packham (@leepa)
- Jesse Mullan (@jmullan)
+- Kwok-kuen Cheung (@cheungpat)
+- Johan Bloemberg (@aequitas)
+- Dan Watson (@dcwatson)
+- Éric Araujo (@merwok)
+- Dan Palmer (@danpalmer)
+- Andy Boot (@bootandy)
+- @m7v8
Documenters
===================
@@ -67,6 +74,7 @@ Documenters
- Elliott Sales de Andrade (@QuLogic)
- Brian Peiris (@brianpeiris)
- Tim Graham (@timgraham)
+- Josh Soref (@jsoref)
--------------------------------------------
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1ef29082..acd26d2d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,7 +28,7 @@ Changelog
### 4.2.0
- Added option "NOQA" Do not wrap lines, but add a noqa statement at the end
-- Added support for runnning isort recursively, simply with a standalone `isort` command
+- Added support for running isort recursively, simply with a standalone `isort` command
- Added support to run isort library as a module
- Added compatibility for Python 3.5
- Fixed performance issue (#338) when running on project with lots of skipped directories
@@ -43,5 +43,8 @@ Changelog
### 4.2.2
- Give an error message when isort is unable to determine where to place a module
-- Allow imports to be sorted by module, independant of import_type, when `force_sort_within_sections` option is set
+- Allow imports to be sorted by module, independent of import_type, when `force_sort_within_sections` option is set
- Fixed an issue that caused Python files with 2 top comments not to be sorted
+
+### 4.2.3
+- Fixed a large number of priority bugs - bug fix only release
diff --git a/README.rst b/README.rst
index f120339c..db35bfb9 100644
--- a/README.rst
+++ b/README.rst
@@ -381,7 +381,7 @@ to your preference:
sections=FUTURE,STDLIB,FIRSTPARTY,THIRDPARTY,LOCALFOLDER
-You also can define your own sections and thier order.
+You also can define your own sections and their order.
Example:
@@ -548,10 +548,11 @@ To cause the commit to fail if there are isort errors (strict mode), include the
.. code-block:: python
+ #!/usr/bin/env python
+ import sys
from isort.hooks import git_hook
- if __name__ == '__main__':
- sys.exit(git_hook(strict=True))
+ 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.
diff --git a/isort/__init__.py b/isort/__init__.py
index 0f09c4a4..6e4bf4c4 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 SortImports
-__version__ = "4.2.2"
+__version__ = "4.2.3"
diff --git a/isort/isort.py b/isort/isort.py
index a29e9868..01051812 100644
--- a/isort/isort.py
+++ b/isort/isort.py
@@ -26,7 +26,6 @@ OTHER DEALINGS IN THE SOFTWARE.
"""
from __future__ import absolute_import, division, print_function, unicode_literals
-import codecs
import copy
import io
import itertools
@@ -36,8 +35,10 @@ import sys
from collections import namedtuple
from datetime import datetime
from difflib import unified_diff
+from fnmatch import fnmatch
+from glob import glob
from sys import path as PYTHONPATH
-from sys import stderr, stdout
+from sys import stdout
from . import settings
from .natural import nsorted
@@ -103,7 +104,7 @@ class SortImports(object):
elif not file_contents:
self.file_path = file_path
self.file_encoding = coding_check(file_path)
- with codecs.open(file_path, encoding=self.file_encoding) 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:
@@ -122,7 +123,7 @@ class SortImports(object):
self.as_map = {}
section_names = self.config.get('sections')
- self.sections = namedtuple('Sections', section_names)(*[n for n in section_names])
+ self.sections = namedtuple('Sections', section_names)(*[name for name in section_names])
for section in itertools.chain(self.sections, self.config['forced_separate']):
self.imports[section] = {'straight': set(), 'from': {}}
@@ -155,7 +156,7 @@ class SortImports(object):
return
if check:
- if self.output == file_contents:
+ if self.output.replace("\n", "").replace(" ", "") == file_contents.replace("\n", "").replace(" ", ""):
if self.config['verbose']:
print("SUCCESS: {0} Everything Looks Good!".format(self.file_path))
else:
@@ -176,12 +177,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 codecs.open(self.file_path, encoding=self.file_encoding, mode='w') 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):
@@ -190,7 +191,8 @@ class SortImports(object):
self.output.splitlines(1),
fromfile=self.file_path + ':before',
tofile=self.file_path + ':after',
- fromfiledate=str(datetime.fromtimestamp(os.path.getmtime(self.file_path))),
+ fromfiledate=str(datetime.fromtimestamp(os.path.getmtime(self.file_path))
+ if self.file_path else datetime.now()),
tofiledate=str(datetime.now())
):
stdout.write(line)
@@ -203,21 +205,26 @@ class SortImports(object):
lines = lines[1:]
return "\n".join(lines)
- def place_module(self, moduleName):
+ def place_module(self, module_name):
"""Tries to determine if a module is a python std import, third party import, or project code:
if it can't determine - it assumes it is project code
"""
for forced_separate in self.config['forced_separate']:
- if moduleName.startswith(forced_separate) or moduleName.startswith("." + forced_separate):
+ # Ensure all forced_separate patterns will match to end of string
+ path_glob = forced_separate
+ if not forced_separate.endswith('*'):
+ path_glob = '%s*' % forced_separate
+
+ if fnmatch(module_name, path_glob) or fnmatch(module_name, '.' + path_glob):
return forced_separate
- if moduleName.startswith("."):
+ if module_name.startswith("."):
return self.sections.LOCALFOLDER
# Try to find most specific placement instruction match (if any)
- parts = moduleName.split('.')
+ parts = module_name.split('.')
module_names_to_check = ['.'.join(parts[:first_k]) for first_k in range(len(parts), 0, -1)]
for module_name_to_check in module_names_to_check:
for placement in reversed(self.sections):
@@ -227,23 +234,23 @@ class SortImports(object):
return placement
paths = PYTHONPATH
- virtual_env = os.environ.get('VIRTUAL_ENV', None)
+ virtual_env = self.config.get('virtual_env') or os.environ.get('VIRTUAL_ENV')
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]))
+ paths += [path for path in glob('{0}/lib/python*/site-packages'.format(virtual_env))
+ if path not in paths]
+ paths += [path for path in glob('{0}/src/*'.format(virtual_env)) if os.path.isdir(path)]
for prefix in paths:
- module_path = "/".join((prefix, moduleName.replace(".", "/")))
- package_path = "/".join((prefix, moduleName.split(".")[0]))
+ 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))):
- if "site-packages" in prefix or "dist-packages" in prefix:
+ if 'site-packages' in prefix or 'dist-packages' in prefix or 'src' in prefix:
return self.sections.THIRDPARTY
- elif "python2" in prefix.lower() or "python3" in prefix.lower():
+ elif 'python2' in prefix.lower() or 'python3' in prefix.lower():
return self.sections.STDLIB
else:
- return self.sections.FIRSTPARTY
+ return self.config['default_section']
return self.config['default_section']
@@ -268,11 +275,15 @@ class SortImports(object):
return self.index == self.number_of_lines
@staticmethod
- def _module_key(module_name, config, sub_imports=False):
+ def _module_key(module_name, config, sub_imports=False, ignore_case=False):
prefix = ""
- module_name = str(module_name)
+ if ignore_case:
+ module_name = str(module_name).lower()
+ else:
+ module_name = str(module_name)
+
if sub_imports and config['order_by_type']:
- if module_name.isupper():
+ if module_name.isupper() and len(module_name) > 1:
prefix = "A"
elif module_name[0:1].isupper():
prefix = "B"
@@ -331,14 +342,14 @@ class SortImports(object):
section_output.extend(comments_above)
section_output.append(self._add_comments(self.comments['straight'].get(module), import_definition))
- def _add_from_imports(self, from_modules, section, section_output):
+ def _add_from_imports(self, from_modules, section, section_output, ignore_case):
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 = nsorted(from_imports, key=lambda key: self._module_key(key, self.config, True))
+ from_imports = nsorted(from_imports, key=lambda key: self._module_key(key, self.config, True, ignore_case))
if self.remove_imports:
from_imports = [line for line in from_imports if not "{0}.{1}".format(module, line) in
self.remove_imports]
@@ -440,67 +451,44 @@ class SortImports(object):
(at the index of the first import) sorted alphabetically and split between groups
"""
- if self.config.get('force_alphabetical_sort', False):
- from_output = []
- straight_output = []
- for section in itertools.chain(self.sections, self.config['forced_separate']):
- straight_modules = list(self.imports[section]['straight'])
- from_modules = list(self.imports[section]['from'].keys())
-
- self._add_from_imports(from_modules, section, from_output)
- self._add_straight_imports(straight_modules, section, straight_output)
-
- new_from_output = []
- new_straight_output = []
- for line in from_output:
- for element in line.split('\n'):
- new_from_output.append(element)
- for line in straight_output:
- for element in line.split('\n'):
- new_straight_output.append(element)
-
-
- sorted_from = sorted(new_from_output, key=lambda import_string: import_string.lower())
- sorted_straight = sorted(new_straight_output, key=lambda import_string: import_string.lower())
- output = (sorted_from + [''] + sorted_straight) if (sorted_from and sorted_straight) else \
- (sorted_from or sorted_straight)
- else:
- output = []
- for section in itertools.chain(self.sections, self.config['forced_separate']):
- straight_modules = list(self.imports[section]['straight'])
- straight_modules = nsorted(straight_modules, key=lambda key: self._module_key(key, self.config))
- from_modules = sorted(list(self.imports[section]['from'].keys()))
- from_modules = nsorted(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 self.config.get('force_sort_within_sections', False):
- def by_module(line):
- line = re.sub('^from ', '', line)
- line = re.sub('^import ', '', line)
- if not self.config['order_by_type']:
- line = line.lower()
- return line
- section_output = nsorted(section_output, key=by_module)
-
- if section_output:
- section_name = section
- if section_name in self.place_imports:
- self.place_imports[section_name] = section_output
- continue
-
- section_title = self.config.get('import_heading_' + str(section_name).lower(), '')
- if section_title:
- section_comment = "# {0}".format(section_title)
- if not section_comment in self.out_lines[0:1]:
- section_output.insert(0, section_comment)
- output += section_output + ['']
+ sort_ignore_case = self.config.get('force_alphabetical_sort', False)
+
+ output = []
+ for section in itertools.chain(self.sections, self.config['forced_separate']):
+ straight_modules = list(self.imports[section]['straight'])
+ straight_modules = nsorted(straight_modules, key=lambda key: self._module_key(key, self.config))
+ from_modules = sorted(list(self.imports[section]['from'].keys()))
+ from_modules = nsorted(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, sort_ignore_case)
+ 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, sort_ignore_case)
+
+ if self.config.get('force_sort_within_sections', False):
+ def by_module(line):
+ line = re.sub('^from ', '', line)
+ line = re.sub('^import ', '', line)
+ if not self.config['order_by_type']:
+ line = line.lower()
+ return line
+ section_output = nsorted(section_output, key=by_module)
+
+ if section_output:
+ section_name = section
+ if section_name in self.place_imports:
+ self.place_imports[section_name] = section_output
+ continue
+
+ section_title = self.config.get('import_heading_' + str(section_name).lower(), '')
+ if section_title:
+ section_comment = "# {0}".format(section_title)
+ if not section_comment in self.out_lines[0:1]:
+ section_output.insert(0, section_comment)
+ output += section_output + ([''] * self.config['lines_between_sections'])
while [character.strip() for character in output[-1:]] == [""]:
output.pop()
@@ -604,7 +592,7 @@ class SortImports(object):
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))
+ retval = '{0}{1}'.format(statement, ', '.join(imports))
comment_str = ' '.join(comments)
if comments:
if len(retval) + 4 + len(comment_str) <= line_length:
@@ -711,6 +699,7 @@ class SortImports(object):
self._in_top_comment = False
while not self._at_end():
line = self._get_line()
+ statement_index = self.index
skip_line = self._skip_line(line)
if line in self._section_comments and not skip_line:
@@ -718,10 +707,10 @@ class SortImports(object):
self.import_index = self.index - 1
continue
- if "isort:" + "imports-" in line and line.startswith("#"):
- section = line.split("isort:" + "imports-")[-1].split()[0]
- self.place_imports[section.upper()] = []
- self.import_placements[line] = section.upper()
+ if "isort:imports-" in line and line.startswith("#"):
+ section = line.split("isort:imports-")[-1].split()[0].upper()
+ self.place_imports[section] = []
+ self.import_placements[line] = section
if ";" in line:
for part in (part.strip() for part in line.split(";")):
@@ -769,6 +758,7 @@ class SortImports(object):
import_string = import_string.rstrip().rstrip("\\") + line.lstrip()
if import_type == "from":
+ import_string = import_string.replace("import(", "import (")
parts = import_string.split(" import ")
from_import = parts[0].split(" ")
import_string = " import ".join([from_import[0] + " " + "".join(from_import[1:])] + parts[1:])
@@ -798,22 +788,23 @@ class SortImports(object):
)
root = self.imports[placed_module][import_type]
for import_name in imports:
- associated_commment = nested_comments.get(import_name)
- if associated_commment:
- self.comments['nested'].setdefault(import_from, {})[import_name] = associated_commment
- comments.pop(comments.index(associated_commment))
+ associated_comment = nested_comments.get(import_name)
+ if associated_comment:
+ self.comments['nested'].setdefault(import_from, {})[import_name] = associated_comment
+ comments.pop(comments.index(associated_comment))
if comments:
self.comments['from'].setdefault(import_from, []).extend(comments)
if len(self.out_lines) > max(self.import_index, self._first_comment_index_end, 1) - 1:
last = self.out_lines and self.out_lines[-1].rstrip() or ""
- while last.startswith("#") and not last.endswith('"""') and not last.endswith("'''"):
+ 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:
last = self.out_lines[-1].rstrip()
else:
last = ""
- if self.index - 1 == self.import_index:
+ if statement_index - 1 == self.import_index:
self.import_index -= len(self.comments['above']['from'].get(import_from, []))
if root.get(import_from, False):
@@ -828,7 +819,8 @@ class SortImports(object):
if len(self.out_lines) > max(self.import_index, self._first_comment_index_end, 1) - 1:
last = self.out_lines and self.out_lines[-1].rstrip() or ""
- while last.startswith("#") and not last.endswith('"""') and not last.endswith("'''"):
+ while (last.startswith("#") and not last.endswith('"""') and not last.endswith("'''")
+ 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,
diff --git a/isort/main.py b/isort/main.py
index d09ca914..2a99da4a 100755
--- a/isort/main.py
+++ b/isort/main.py
@@ -61,18 +61,18 @@ def iter_source_code(paths, config, skipped):
"""Iterate over all Python source files defined in paths."""
for path in paths:
if os.path.isdir(path):
- if should_skip(path, config):
+ if should_skip(path, config, os.getcwd()):
skipped.append(path)
continue
for dirpath, dirnames, filenames in os.walk(path, topdown=True):
for dirname in list(dirnames):
- if should_skip(dirname, config):
+ if should_skip(dirname, config, dirpath):
skipped.append(dirname)
dirnames.remove(dirname)
for filename in filenames:
if filename.endswith('.py'):
- if should_skip(filename, config):
+ if should_skip(filename, config, dirpath):
skipped.append(filename)
else:
yield os.path.join(dirpath, filename)
@@ -181,8 +181,10 @@ 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('-sl', '--force_single_line_imports', dest='force_single_line', action='store_true',
+ 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',
+ help=argparse.SUPPRESS)
parser.add_argument('-sd', '--section-default', dest='default_section',
help='Sets the default section for imports (by default FIRSTPARTY) options: ' +
str(DEFAULT_SECTIONS))
@@ -195,6 +197,8 @@ def create_parser():
help='Recursively look for Python files of which to sort imports')
parser.add_argument('-ot', '--order-by-type', dest='order_by_type',
action='store_true', help='Order imports by type in addition to alphabetically')
+ parser.add_argument('-dt', '--dont-order-by-type', dest='dont_order_by_type',
+ action='store_true', help='Only order imports alphabetically, do not attempt type ordering')
parser.add_argument('-ac', '--atomic', dest='atomic', action='store_true',
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',
@@ -219,10 +223,12 @@ def create_parser():
parser.add_argument('-fas', '--force-alphabetical-sort', action='store_true', dest="force_alphabetical_sort",
help='Force all imports to be sorted as a single section')
parser.add_argument('-fss', '--force-sort-within-sections', action='store_true', dest="force_sort_within_sections",
- help='Force imports to be sorted by module, independant of import_type')
+ help='Force imports to be sorted by module, independent of import_type')
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
diff --git a/isort/natural.py b/isort/natural.py
index a20ba1d4..0529fa60 100644
--- a/isort/natural.py
+++ b/isort/natural.py
@@ -38,9 +38,10 @@ def _natural_keys(text):
def nsorted(to_sort, key=None):
"""Returns a naturally sorted list"""
- if not key:
+ if key is None:
key_callback = _natural_keys
else:
- key_callback = lambda item: _natural_keys(key(item))
+ def key_callback(item):
+ return _natural_keys(key(item))
return sorted(to_sort, key=key_callback)
diff --git a/isort/pie_slice.py b/isort/pie_slice.py
index 5cef39b1..d8a35769 100644
--- a/isort/pie_slice.py
+++ b/isort/pie_slice.py
@@ -6,10 +6,10 @@ 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 copie_slice of the Software, and
+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 copie_slice or
+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
diff --git a/isort/pylama_isort.py b/isort/pylama_isort.py
index a04b62bd..6fa235f9 100644
--- a/isort/pylama_isort.py
+++ b/isort/pylama_isort.py
@@ -25,3 +25,5 @@ class Linter(BaseLinter):
'text': 'Incorrectly sorted imports.',
'type': 'ISORT'
}]
+ else:
+ return []
diff --git a/isort/settings.py b/isort/settings.py
index d7cd3177..df5552eb 100644
--- a/isort/settings.py
+++ b/isort/settings.py
@@ -68,7 +68,7 @@ default = {'force_to_top': [],
"timeit", "trace", "traceback", "unittest", "urllib", "urllib2", "urlparse",
"usercustomize", "uuid", "warnings", "weakref", "webbrowser", "whichdb", "xml",
"xmlrpclib", "zipfile", "zipimport", "zlib", 'builtins', '__builtin__', 'thread',
- "binascii", "statistics", "unicodedata", "fcntl"],
+ "binascii", "statistics", "unicodedata", "fcntl", 'pathlib'],
'known_third_party': ['google.appengine.api'],
'known_first_party': [],
'multi_line_output': WrapModes.GRID,
@@ -89,12 +89,18 @@ default = {'force_to_top': [],
'order_by_type': True,
'atomic': False,
'lines_after_imports': -1,
+ 'lines_between_sections': 1,
'combine_as_imports': False,
'combine_star': False,
'include_trailing_comma': False,
'from_first': False,
'verbose': False,
- 'quiet': False}
+ 'quiet': False,
+ 'force_adds': False,
+ 'force_alphabetical_sort': False,
+ 'force_grid_wrap': False,
+ 'force_sort_within_sections': False,
+ 'show_diff': False}
@lru_cache()
@@ -165,7 +171,7 @@ def _update_with_config_file(file_path, sections, computed_settings):
def _as_list(value):
- return filter(bool, [item.strip() for item in value.split(",")])
+ return filter(bool, [item.strip() for item in value.replace('\n', ',').split(",")])
@lru_cache()
@@ -193,10 +199,11 @@ def _get_config_data(file_path, sections):
return {}
-def should_skip(filename, config):
+def should_skip(filename, config, path='/'):
"""Returns True if the file should be skipped based on the passed in settings."""
for skip_path in config['skip']:
- if skip_path.endswith(filename):
+ if os.path.join(path, filename).endswith('/' + skip_path.lstrip('/')):
+ print(skip_path)
return True
position = os.path.split(filename)
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 00000000..5a72af01
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,2 @@
+pytest==2.9.1
+ipython==4.1.2
diff --git a/setup.py b/setup.py
index e3704d00..f22774ea 100755
--- a/setup.py
+++ b/setup.py
@@ -39,13 +39,13 @@ with open('README.rst', 'r') as f:
readme = f.read()
setup(name='isort',
- version='4.2.2',
+ version='4.2.3',
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/4.2.2.tar.gz',
+ download_url='https://github.com/timothycrosley/isort/archive/4.2.3.tar.gz',
license="MIT",
entry_points={
'console_scripts': [
diff --git a/test_isort.py b/test_isort.py
index ee9a49ce..955efd0d 100644
--- a/test_isort.py
+++ b/test_isort.py
@@ -347,7 +347,7 @@ def test_output_modes():
output_noqa = SortImports(file_contents=REALLY_LONG_IMPORT_WITH_COMMENT,
multi_line_output=WrapModes.NOQA).output
- assert output_noqa == "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 # NOQA comment\n" # NOQA
+ assert output_noqa == "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 # NOQA comment\n" # NOQA
def test_qa_comment_case():
@@ -1193,7 +1193,7 @@ def test_same_line_statements():
def test_long_line_comments():
- """Ensure isort correctly handles comments at the end of extreamly long lines"""
+ """Ensure isort correctly handles comments at the end of extremely long lines"""
test_input = ("from foo.utils.fabric_stuff.live import check_clean_live, deploy_live, sync_live_envdir, "
"update_live_app, update_live_cron # noqa\n"
"from foo.utils.fabric_stuff.stage import check_clean_stage, deploy_stage, sync_stage_envdir, "
@@ -1232,18 +1232,22 @@ def test_place_comments():
"print('code')\n"
"\n"
"# isort:imports-stdlib\n")
+ expected_output = ("\n# isort:imports-thirdparty\n"
+ "import django.settings\n"
+ "\n"
+ "# isort:imports-firstparty\n"
+ "import myproject.test\n"
+ "\n"
+ "print('code')\n"
+ "\n"
+ "# isort:imports-stdlib\n"
+ "import os\n"
+ "import sys\n")
test_output = SortImports(file_contents=test_input, known_third_party=['django']).output
- assert test_output == ("\n# isort:imports-thirdparty\n"
- "import django.settings\n"
- "\n"
- "# isort:imports-firstparty\n"
- "import myproject.test\n"
- "\n"
- "print('code')\n"
- "\n"
- "# isort:imports-stdlib\n"
- "import os\n"
- "import sys\n")
+ assert test_output == expected_output
+ test_output = SortImports(file_contents=test_output, known_third_party=['django']).output
+ assert test_output == expected_output
+
def test_placement_control():
@@ -1494,24 +1498,35 @@ def test_comment_at_top_of_file():
def test_alphabetic_sorting():
"""Test to ensure isort correctly handles top of file comments"""
- test_input = ("from django.contrib.gis.geos import GEOSException\n"
+ test_input = ("import unittest\n"
+ "\n"
+ "import ABC\n"
+ "import Zope\n"
+ "from django.contrib.gis.geos import GEOSException\n"
"from plone.app.testing import getRoles\n"
"from plone.app.testing import ManageRoles\n"
"from plone.app.testing import setRoles\n"
"from Products.CMFPlone import utils\n"
- "\n"
- "import ABC\n"
- "import unittest\n"
- "import Zope\n")
+ )
options = {'force_single_line': True,
'force_alphabetical_sort': True, }
- assert SortImports(file_contents=test_input, **options).output == test_input
+
+ output = SortImports(file_contents=test_input, **options).output
+ assert output == test_input
test_input = ("# -*- coding: utf-8 -*-\n"
"from django.db import models\n")
assert SortImports(file_contents=test_input).output == test_input
+def test_alphabetic_sorting_multi_line():
+ """Test to ensure isort correctly handles multiline import see: issue 364"""
+ test_input = ("from a import (CONSTANT_A, cONSTANT_B, CONSTANT_C, CONSTANT_D, CONSTANT_E,\n"
+ " CONSTANT_F, CONSTANT_G, CONSTANT_H, CONSTANT_I, CONSTANT_J)\n")
+ options = {'force_alphabetical_sort': True, }
+ assert SortImports(file_contents=test_input, **options).output == test_input
+
+
def test_comments_not_duplicated():
"""Test to ensure comments aren't duplicated: issue 303"""
test_input = ('from flask import url_for\n'
@@ -1596,11 +1611,11 @@ def test_alphabetic_sorting_no_newlines():
test_output = SortImports(file_contents=test_input,force_alphabetical_sort=True).output
assert test_input == test_output
- test_input = ('from a import b\n'
- '\n'
- 'import os\n'
+ test_input = ('import os\n'
'import unittest\n'
'\n'
+ 'from a import b\n'
+ '\n'
'\n'
'print(1)\n')
test_output = SortImports(file_contents=test_input,force_alphabetical_sort=True, lines_after_imports=2).output
@@ -1638,3 +1653,101 @@ def test_sorting_with_two_top_comments():
"'''\n"
'import a\n'
'import b\n')
+
+
+def test_lines_between_sections():
+ """Test to ensure lines_between_sections works"""
+ test_input = ('from bar import baz\n'
+ 'import os\n')
+ assert SortImports(file_contents=test_input, lines_between_sections=0).output == ('import os\n'
+ 'from bar import baz\n')
+ 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'
+ '\n'
+ 'from myproject.foo.models import Foo\n'
+ '\n'
+ 'from myproject.utils import util_method\n'
+ '\n'
+ 'from myproject.bar.models import Bar\n'
+ '\n'
+ 'import sys\n')
+ test_output = SortImports(file_contents=test_input, forced_separate=['*.models'],
+ line_length=120).output
+
+ assert test_output == ('import os\n'
+ 'import sys\n'
+ '\n'
+ 'from myproject.utils import util_method\n'
+ '\n'
+ 'from myproject.bar.models import Bar\n'
+ 'from myproject.foo.models import Foo\n')
+
+
+def test_no_additional_lines_issue_358():
+ """Test to ensure issue 358 is resovled and running isort multiple times does not add extra newlines"""
+ test_input = ('"""This is a docstring"""\n'
+ '# This is a comment\n'
+ 'from __future__ import (\n'
+ ' absolute_import,\n'
+ ' division,\n'
+ ' print_function,\n'
+ ' unicode_literals\n'
+ ')\n')
+ expected_output = ('"""This is a docstring"""\n'
+ '# This is a comment\n'
+ 'from __future__ import (\n'
+ ' absolute_import,\n'
+ ' division,\n'
+ ' print_function,\n'
+ ' unicode_literals\n'
+ ')\n')
+ test_output = SortImports(file_contents=test_input, multi_line_output=3, line_length=20).output
+ assert test_output == expected_output
+
+ test_output = SortImports(file_contents=test_output, multi_line_output=3, line_length=20).output
+ assert test_output == expected_output
+
+ for attempt in range(5):
+ test_output = SortImports(file_contents=test_output, multi_line_output=3, line_length=20).output
+ assert test_output == expected_output
+
+ test_input = ('"""This is a docstring"""\n'
+ '\n'
+ '# This is a comment\n'
+ 'from __future__ import (\n'
+ ' absolute_import,\n'
+ ' division,\n'
+ ' print_function,\n'
+ ' unicode_literals\n'
+ ')\n')
+ expected_output = ('"""This is a docstring"""\n'
+ '\n'
+ '# This is a comment\n'
+ 'from __future__ import (\n'
+ ' absolute_import,\n'
+ ' division,\n'
+ ' print_function,\n'
+ ' unicode_literals\n'
+ ')\n')
+ test_output = SortImports(file_contents=test_input, multi_line_output=3, line_length=20).output
+ assert test_output == expected_output
+
+ test_output = SortImports(file_contents=test_output, multi_line_output=3, line_length=20).output
+ assert test_output == expected_output
+
+ for attempt in range(5):
+ test_output = SortImports(file_contents=test_output, multi_line_output=3, line_length=20).output
+ assert test_output == expected_output
+
+
+def test_import_by_paren_issue_375():
+ """Test to ensure isort can correctly handle sorting imports where the paren is directly by the import body"""
+ test_input = ('from .models import(\n'
+ ' Foo,\n'
+ ' Bar,\n'
+ ')\n')
+ assert SortImports(file_contents=test_input).output == 'from .models import Bar, Foo\n'
diff --git a/tox.ini b/tox.ini
index da1940a0..781d14d2 100644
--- a/tox.ini
+++ b/tox.ini
@@ -6,11 +6,11 @@
[tox]
envlist =
isort-check,
- py26, py27, py32, py33, py34, py35, pypy
+ py26, py27, py33, py34, py35, pypy
[testenv]
commands =
- py.test {posargs}
+ py.test test_isort.py {posargs}
deps =
pytest