diff --git a/ACKNOWLEDGEMENTS.md b/ACKNOWLEDGEMENTS.md index 9b173ec00..99c7873a5 100644 --- a/ACKNOWLEDGEMENTS.md +++ b/ACKNOWLEDGEMENTS.md @@ -14,6 +14,7 @@ Notable Bug Reporters - Chris Adams (@acdha) - @OddBloke - Martin Geisler (@mgeisler) +- Tim Heap (@timheap) Code Contributors =================== diff --git a/CHANGELOG.md b/CHANGELOG.md index acd26d2da..335129d80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,3 +48,10 @@ Changelog ### 4.2.3 - Fixed a large number of priority bugs - bug fix only release + +### 4.2.4 +- Fixed an issue that caused module that contained functions before doc strings, to incorrectly place imports +- Fixed regression in how `force_alphabetical_sort` was being interpretted (issue #409) +- Fixed stray print statement printing skipped files (issue #411) +- Added option for forcing imports into a single bucket: `no_sections` +- Added option for new lines between import types (from, straight): `lines_between_sections` diff --git a/isort/__init__.py b/isort/__init__.py index 6e4bf4c48..90757d7dc 100644 --- a/isort/__init__.py +++ b/isort/__init__.py @@ -25,4 +25,4 @@ from . import settings from .isort import SortImports -__version__ = "4.2.3" +__version__ = "4.2.4" diff --git a/isort/isort.py b/isort/isort.py index 010518122..f897bcbed 100644 --- a/isort/isort.py +++ b/isort/isort.py @@ -74,6 +74,12 @@ def __init__(self, file_path=None, file_contents=None, write_to_stdout=False, ch else: self.config[key] = value + if self.config.get('force_alphabetical_sort', False): + self.config.update({'force_alphabetical_sort_within_sections': True, + 'no_sections': True, + 'lines_between_types': 1, + 'from_first': True}) + indent = str(self.config['indent']) if indent.isdigit(): indent = " " * int(indent) @@ -451,10 +457,19 @@ def _add_formatted_imports(self): (at the index of the first import) sorted alphabetically and split between groups """ - sort_ignore_case = self.config.get('force_alphabetical_sort', False) + sort_ignore_case = self.config.get('force_alphabetical_sort_within_sections', False) + sections = itertools.chain(self.sections, self.config['forced_separate']) + + sections = itertools.chain(self.sections, self.config['forced_separate']) + if self.config.get('no_sections', False): + self.imports['no_sections'] = {'straight': [], 'from': {}} + for section in sections: + self.imports['no_sections']['straight'].extend(self.imports[section].get('straight', [])) + self.imports['no_sections']['from'].update(self.imports[section].get('from', {})) + sections = ('no_sections', ) output = [] - for section in itertools.chain(self.sections, self.config['forced_separate']): + for section in sections: 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())) @@ -463,9 +478,13 @@ def _add_formatted_imports(self): section_output = [] if self.config.get('from_first', False): self._add_from_imports(from_modules, section, section_output, sort_ignore_case) + if self.config.get('lines_between_types', 0) and from_modules and straight_modules: + section_output.extend([''] * self.config['lines_between_types']) self._add_straight_imports(straight_modules, section, section_output) else: self._add_straight_imports(straight_modules, section, section_output) + if self.config.get('lines_between_types', 0) and from_modules and straight_modules: + section_output.extend([''] * self.config['lines_between_types']) self._add_from_imports(from_modules, section, section_output, sort_ignore_case) if self.config.get('force_sort_within_sections', False): @@ -658,7 +677,7 @@ def _skip_line(self, line): if '"' in line or "'" in line: index = 0 - if self._first_comment_index_start == -1: + if self._first_comment_index_start == -1 and (line.startswith('"') or line.startswith("'")): self._first_comment_index_start = self.index while index < len(line): if line[index] == "\\": diff --git a/isort/main.py b/isort/main.py index 2a99da4ac..600fbad54 100755 --- a/isort/main.py +++ b/isort/main.py @@ -185,6 +185,8 @@ def create_parser(): 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('-ds', '--no-sections', help='Put all imports into the same section bucket', dest='no_sections', + action='store_true') parser.add_argument('-sd', '--section-default', dest='default_section', help='Sets the default section for imports (by default FIRSTPARTY) options: ' + str(DEFAULT_SECTIONS)) @@ -220,11 +222,14 @@ def create_parser(): help="Specifies how long lines that are wrapped should be, if not set line_length is used.") parser.add_argument('-fgw', '--force-grid-wrap', action='store_true', dest="force_grid_wrap", help='Force from imports to be grid wrapped regardless of line length') + parser.add_argument('-fass', '--force-alphabetical-sort-within-sections', action='store_true', + dest="force_alphabetical_sort", help='Force all imports to be sorted alphabetically within a ' + 'section') 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, independent of import_type') - + parser.add_argument('-lbt', '--lines-between-types', dest='lines_between_types', type=int) arguments = dict((key, value) for (key, value) in itemsview(vars(parser.parse_args())) if value) if 'dont_order_by_type' in arguments: diff --git a/isort/settings.py b/isort/settings.py index df5552ebd..26cec5bd5 100644 --- a/isort/settings.py +++ b/isort/settings.py @@ -48,6 +48,7 @@ 'line_length': 79, 'wrap_length': 0, 'sections': DEFAULT_SECTIONS, + 'no_sections': False, 'known_future_library': ['__future__'], 'known_standard_library': ["abc", "anydbm", "argparse", "array", "asynchat", "asyncore", "atexit", "base64", "BaseHTTPServer", "bisect", "bz2", "calendar", "cgitb", "cmd", "codecs", @@ -90,6 +91,7 @@ 'atomic': False, 'lines_after_imports': -1, 'lines_between_sections': 1, + 'lines_between_types': 0, 'combine_as_imports': False, 'combine_star': False, 'include_trailing_comma': False, @@ -97,6 +99,7 @@ 'verbose': False, 'quiet': False, 'force_adds': False, + 'force_alphabetical_sort_within_sections': False, 'force_alphabetical_sort': False, 'force_grid_wrap': False, 'force_sort_within_sections': False, @@ -203,7 +206,6 @@ 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 os.path.join(path, filename).endswith('/' + skip_path.lstrip('/')): - print(skip_path) return True position = os.path.split(filename) diff --git a/setup.py b/setup.py index f22774ea9..1397bfeb9 100755 --- a/setup.py +++ b/setup.py @@ -39,13 +39,13 @@ def run(self): readme = f.read() setup(name='isort', - version='4.2.3', + version='4.2.4', 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.3.tar.gz', + download_url='https://github.com/timothycrosley/isort/archive/4.2.4.tar.gz', license="MIT", entry_points={ 'console_scripts': [ diff --git a/test_isort.py b/test_isort.py index 955efd0dc..8b924fd25 100644 --- a/test_isort.py +++ b/test_isort.py @@ -1509,7 +1509,7 @@ def test_alphabetic_sorting(): "from Products.CMFPlone import utils\n" ) options = {'force_single_line': True, - 'force_alphabetical_sort': True, } + 'force_alphabetical_sort_within_sections': True, } output = SortImports(file_contents=test_input, **options).output assert output == test_input @@ -1523,7 +1523,7 @@ 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, } + options = {'force_alphabetical_sort_within_sections': True, } assert SortImports(file_contents=test_input, **options).output == test_input @@ -1608,7 +1608,7 @@ def test_sections_parsed_correct(): def test_alphabetic_sorting_no_newlines(): '''Test to ensure that alphabetical sort does not erroneously introduce new lines (issue #328)''' test_input = "import os\n" - test_output = SortImports(file_contents=test_input,force_alphabetical_sort=True).output + test_output = SortImports(file_contents=test_input,force_alphabetical_sort_within_sections=True).output assert test_input == test_output test_input = ('import os\n' @@ -1618,7 +1618,7 @@ def test_alphabetic_sorting_no_newlines(): '\n' '\n' 'print(1)\n') - test_output = SortImports(file_contents=test_input,force_alphabetical_sort=True, lines_after_imports=2).output + test_output = SortImports(file_contents=test_input,force_alphabetical_sort_within_sections=True, lines_after_imports=2).output assert test_input == test_output @@ -1751,3 +1751,34 @@ def test_import_by_paren_issue_375(): ' Bar,\n' ')\n') assert SortImports(file_contents=test_input).output == 'from .models import Bar, Foo\n' + + +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'] + test_input = ('def foo():\n' + ' """ Single line triple quoted doctring """\n' + ' pass\n') + expected_output = ('from __future__ import unicode_literals\n' + '\n' + '\n' + 'def foo():\n' + ' """ Single line triple quoted doctring """\n' + ' pass\n') + assert SortImports(file_contents=test_input, add_imports=add_imports).output == expected_output + + +def test_plone_style(): + """Test to ensure isort correctly plone style imports""" + test_input = ("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