diff options
author | Timothy Crosley <tcrosley@domaintools.com> | 2017-06-01 14:23:35 -0700 |
---|---|---|
committer | Timothy Crosley <tcrosley@domaintools.com> | 2017-06-01 14:23:35 -0700 |
commit | 73b5adc97d665e31d3ef6146ed852ece11aea837 (patch) | |
tree | 518793c1f0480c3705c7f597c9010774ecebd6be | |
parent | b2090cc6555a65bd1eea02c12d10cae8a2e042ff (diff) | |
parent | ec2b9f15b1ada1f34b83a56d5c4de311fca6ab7a (diff) | |
download | isort-73b5adc97d665e31d3ef6146ed852ece11aea837.tar.gz |
Merge branch 'master' of https://github.com/timothycrosley/isort into develop
-rw-r--r-- | .travis.yml | 2 | ||||
-rw-r--r-- | CHANGELOG.md | 9 | ||||
-rw-r--r-- | README.rst | 2 | ||||
-rw-r--r-- | appveyor.yml | 2 | ||||
-rw-r--r-- | isort/__init__.py | 2 | ||||
-rw-r--r-- | isort/isort.py | 24 | ||||
-rwxr-xr-x | isort/main.py | 2 | ||||
-rw-r--r-- | isort/pie_slice.py | 193 | ||||
-rw-r--r-- | isort/settings.py | 3 | ||||
-rwxr-xr-x | setup.py | 3 | ||||
-rw-r--r-- | test_isort.py | 18 | ||||
-rw-r--r-- | tox.ini | 2 |
12 files changed, 235 insertions, 27 deletions
diff --git a/.travis.yml b/.travis.yml index cc8354b6..6b23c0db 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,8 @@ language: python matrix: include: - env: TOXENV=isort-check + - python: 2.6 + env: TOXENV=py26 - python: 2.7 env: TOXENV=py27 - python: 3.3 diff --git a/CHANGELOG.md b/CHANGELOG.md index c55055d9..b16ae902 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,14 @@ Changelog ========= -### 4.2.9 - June 1, 2017 - hotfix release +### 4.2.12 - June 1, 2017 - hotfix release IMPORTANT NOTE: This will be the last release with Python 2.6 support, subsequent releases will be 2.7+ only +- Fixed wheel distribution bug + +### 4.2.11 - June 1, 2017 - hotfix release +- Fixed #546: Can't select y/n/c after latest update +- Fixed #545: Incorrectly moves __future__ imports above encoding comments + +### 4.2.9 - June 1, 2017 - hotfix release - Fixed #428: Check only modifies sorting - Fixed #540: Not correctly identifying stdlib modules @@ -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 3b6746b7..003d2e58 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,4 +9,4 @@ install: - pip install tox test_script: - - tox -e isort-check,py27,py33,py34,py35,py36 + - tox -e isort-check,py26,py27,py33,py34,py35,py36 diff --git a/isort/__init__.py b/isort/__init__.py index d906c27d..a6b3a0ab 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.9" +__version__ = "4.2.12" diff --git a/isort/isort.py b/isort/isort.py index b5896df9..d9b4f31b 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, itemsview +from .pie_slice import OrderedDict, OrderedSet, input, itemsview KNOWN_SECTION_MAPPING = { 'STDLIB': 'STANDARD_LIBRARY', @@ -264,7 +263,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(".", "/"))) @@ -489,7 +488,7 @@ class SortImports(object): 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 @@ -903,7 +902,8 @@ class SortImports(object): self.comments['straight'][module] = comments comments = None - if len(self.out_lines) > max(self.import_index, self._first_comment_index_end, 1) - 1: + 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 not 'isort:imports-' in last): @@ -942,6 +942,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. diff --git a/isort/main.py b/isort/main.py index fd192338..eae7afa5 100755 --- a/isort/main.py +++ b/isort/main.py @@ -236,7 +236,7 @@ def create_parser(): parser.add_argument('-up', '--use-parentheses', dest='use_parentheses', action='store_true', 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 diff --git a/isort/pie_slice.py b/isort/pie_slice.py index 359ff25c..bfb341a7 100644 --- a/isort/pie_slice.py +++ b/isort/pie_slice.py @@ -47,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): @@ -85,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) @@ -100,6 +120,7 @@ if PY3: import builtins from urllib import parse + input = input integer_types = (int, ) def u(string): @@ -260,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 @@ -271,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: @@ -317,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): diff --git a/isort/settings.py b/isort/settings.py index e9efe4ea..c60bd786 100644 --- a/isort/settings.py +++ b/isort/settings.py @@ -25,7 +25,6 @@ OTHER DEALINGS IN THE SOFTWARE. from __future__ import absolute_import, division, print_function, unicode_literals import fnmatch -import io import os import posixpath from collections import namedtuple @@ -203,7 +202,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() @@ -39,7 +39,7 @@ with open('README.rst', 'r') as f: readme = f.read() setup(name='isort', - version='4.2.9', + version='4.2.12', description='A Python utility / library to sort Python imports.', long_description=readme, author='Timothy Crosley', @@ -63,6 +63,7 @@ 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', diff --git a/test_isort.py b/test_isort.py index 492d0452..67eccc03 100644 --- a/test_isort.py +++ b/test_isort.py @@ -1002,7 +1002,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(): @@ -2116,3 +2116,19 @@ def test_correct_number_of_new_lines_with_comment_issue_435(): 'def baz():\n' ' pass\n') assert SortImports(file_contents=test_input).output == test_input + + +def test_future_below_encoding_issue_545(): + """Test to ensure future is always below comment""" + test_input = ('#!/usr/bin/env python\n' + 'from __future__ import print_function\n' + 'import logging\n' + '\n' + 'print("hello")\n') + expected_output = ('#!/usr/bin/env python\n' + 'from __future__ import print_function\n' + '\n' + 'import logging\n' + '\n' + 'print("hello")\n') + assert SortImports(file_contents=test_input).output == expected_output @@ -6,7 +6,7 @@ [tox] envlist = isort-check, - py27, py33, py34, py35, py36, pypy + py26, py27, py33, py34, py35, py36, pypy [testenv] commands = |