diff options
-rw-r--r-- | ACKNOWLEDGEMENTS.md | 3 | ||||
-rw-r--r-- | isort/isort.py | 61 | ||||
-rw-r--r-- | test_isort.py | 84 |
3 files changed, 133 insertions, 15 deletions
diff --git a/ACKNOWLEDGEMENTS.md b/ACKNOWLEDGEMENTS.md index fede772d..4fcfe64b 100644 --- a/ACKNOWLEDGEMENTS.md +++ b/ACKNOWLEDGEMENTS.md @@ -86,6 +86,9 @@ Code Contributors - Gram (@orsinium) - Hugo van Kemenade (@hugovk) - Géry Ogam (@maggyero) +- Cody Scott (@Siecje) +- Pedro Algarvio (@s0undt3ch) +- Chris St. Pierre (@stpierre) Documenters =================== diff --git a/isort/isort.py b/isort/isort.py index 48f65453..1e9f2447 100644 --- a/isort/isort.py +++ b/isort/isort.py @@ -66,6 +66,9 @@ if TYPE_CHECKING: class _SortImports: + _import_line_intro_re = re.compile("^(?:from|import) ") + _import_line_midline_import_re = re.compile(" import ") + def __init__( self, file_contents: str, config: Dict[str, Any], extension: str = "py" ) -> None: @@ -273,7 +276,11 @@ class _SortImports: ) and not line_without_comment.strip().startswith(splitter): line_parts = re.split(exp, line_without_comment) if comment: - line_parts[-1] = "{}#{}".format(line_parts[-1], comment) + line_parts[-1] = "{}{} #{}".format( + line_parts[-1].strip(), + "," if self.config["include_trailing_comma"] else "", + comment, + ) next_line = [] while (len(line) + 2) > ( self.config["wrap_length"] or self.config["line_length"] @@ -295,7 +302,9 @@ class _SortImports: splitter, self.line_separator, cont_line, - "," if self.config["include_trailing_comma"] else "", + "," + if self.config["include_trailing_comma"] and not comment + else "", self.line_separator if wrap_mode in { @@ -426,7 +435,7 @@ class _SortImports: ) from_imports = None elif self.config["force_single_line"]: - import_statements = [] + import_statement = None while from_imports: from_import = from_imports.pop(0) single_import_line = self._add_comments( @@ -447,20 +456,19 @@ class _SortImports: self.config["keep_direct_and_as_imports"] and self.imports[section]["from"][module][from_import] ): - import_statements.append(self._wrap(single_import_line)) + section_output.append(self._wrap(single_import_line)) from_comments = self.comments["straight"].get( "{}.{}".format(module, from_import) ) - import_statements.extend( + section_output.extend( self._add_comments( from_comments, self._wrap(import_start + as_import) ) for as_import in nsorted(as_imports[from_import]) ) else: - import_statements.append(self._wrap(single_import_line)) + section_output.append(self._wrap(single_import_line)) comments = None - import_statement = self.line_separator.join(import_statements) else: while from_imports and from_imports[0] in as_imports: from_import = from_imports.pop(0) @@ -699,6 +707,9 @@ class _SortImports: ), ) + if self.config["force_sort_within_sections"]: + copied_comments = copy.deepcopy(self.comments) + section_output = [] # type: List[str] if self.config["from_first"]: self._add_from_imports( @@ -727,19 +738,37 @@ class _SortImports: def by_module(line: str) -> str: section = "B" - if line.startswith("#"): - return "AA" - line = re.sub("^from ", "", line) - line = re.sub("^import ", "", line) + line = self._import_line_intro_re.sub( + "", self._import_line_midline_import_re.sub(".", line) + ) if line.split(" ")[0] in self.config["force_to_top"]: section = "A" if not self.config["order_by_type"]: line = line.lower() return "{}{}".format(section, line) + # Remove comments + section_output = [ + line for line in section_output if not line.startswith("#") + ] + section_output = nsorted(section_output, key=by_module) + # Add comments back + all_comments = copied_comments["above"]["from"] + all_comments.update(copied_comments["above"]["straight"]) + comment_indexes = {} + for module, comment_list in all_comments.items(): + for idx, line in enumerate(section_output): + if module in line: + comment_indexes[idx] = comment_list + added = 0 + for idx, comment_list in comment_indexes.items(): + for comment in comment_list: + section_output.insert(idx + added, comment) + added += 1 + section_name = section no_lines_before = section_name in self.config["no_lines_before"] @@ -1285,8 +1314,9 @@ class _SortImports: placed_module = self.place_module(import_from) if self.config["verbose"]: print( - "from-type place_module for %s returned %s" - % (import_from, placed_module) + "from-type place_module for {} returned {}".format( + import_from, placed_module + ) ) if placed_module == "": print( @@ -1391,8 +1421,9 @@ class _SortImports: placed_module = self.place_module(module) if self.config["verbose"]: print( - "else-type place_module for %s returned %s" - % (module, placed_module) + "else-type place_module for {} returned {}".format( + module, placed_module + ) ) if placed_module == "": print( diff --git a/test_isort.py b/test_isort.py index 7ca611f4..c7585889 100644 --- a/test_isort.py +++ b/test_isort.py @@ -1066,6 +1066,26 @@ def test_force_single_line_long_imports() -> None: ) +def test_force_single_line_imports_and_sort_within_sections() -> None: + test_input = ( + "from third_party import lib_a, lib_b, lib_d\n" + "from third_party.lib_c import lib1\n" + ) + test_output = SortImports( + file_contents=test_input, + multi_line_output=WrapModes.GRID, + line_length=40, + force_single_line=True, + force_sort_within_sections=True, + ).output + assert test_output == ( + "from third_party import lib_a\n" + "from third_party import lib_b\n" + "from third_party.lib_c import lib1\n" + "from third_party import lib_d\n" + ) + + def test_titled_imports() -> None: """Tests setting custom titled/commented import sections.""" test_input = ( @@ -1523,6 +1543,26 @@ def test_include_trailing_comma() -> None: "from third_party import (\n lib1,\n)\n" ) + trailing_comma_with_comment = "from six.moves.urllib.parse import urlencode # pylint: disable=no-name-in-module,import-error" + expected_trailing_comma_with_comment = "from six.moves.urllib.parse import (\n urlencode, # pylint: disable=no-name-in-module,import-error\n)\n" + trailing_comma_with_comment = SortImports( + file_contents=trailing_comma_with_comment, + line_length=80, + multi_line_output=WrapModes.VERTICAL_HANGING_INDENT, + include_trailing_comma=True, + use_parentheses=True, + ).output + assert trailing_comma_with_comment == expected_trailing_comma_with_comment + # The next time around, it should be equal + trailing_comma_with_comment = SortImports( + file_contents=trailing_comma_with_comment, + line_length=80, + multi_line_output=WrapModes.VERTICAL_HANGING_INDENT, + include_trailing_comma=True, + use_parentheses=True, + ).output + assert trailing_comma_with_comment == expected_trailing_comma_with_comment + def test_similar_to_std_library() -> None: """Test to ensure modules that are named similarly to a standard library import don't end up clobbered""" @@ -4244,6 +4284,25 @@ def test_isort_ensures_blank_line_between_import_and_comment() -> None: assert SortImports(file_contents=test_input, **config).output == expected_output +def test_moving_comments_issue_726(): + config = {"force_sort_within_sections": 1} # type: Dict[str, Any] + test_input = ( + "import Blue.models as BlueModels\n" + "# comment for PlaidModel\n" + "from Plaid.models import PlaidModel\n" + ) + assert SortImports(file_contents=test_input, **config).output == test_input + + test_input = ( + "# comment for BlueModels\n" + "import Blue.models as BlueModels\n" + "# comment for PlaidModel\n" + "# another comment for PlaidModel\n" + "from Plaid.models import PlaidModel\n" + ) + assert SortImports(file_contents=test_input, **config).output == test_input + + def test_pyi_formatting_issue_942(tmpdir) -> None: test_input = "import os\n\n\ndef my_method():\n" expected_py_output = test_input.splitlines() @@ -4270,6 +4329,31 @@ def test_pyi_formatting_issue_942(tmpdir) -> None: ) +def test_move_class_issue_751() -> None: + test_input = ( + "# -*- coding: utf-8 -*-" + "\n" + "# Define your item pipelines here" + "#" + "# Don't forget to add your pipeline to the ITEM_PIPELINES setting" + "# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html" + "from datetime import datetime" + "from .items import WeiboMblogItem" + "\n" + "class WeiboMblogPipeline(object):" + " def process_item(self, item, spider):" + " if isinstance(item, WeiboMblogItem):" + " item = self._process_item(item, spider)" + " return item" + "\n" + " def _process_item(self, item, spider):" + " item['inserted_at'] = datetime.now()" + " return item" + "\n" + ) + assert SortImports(file_contents=test_input).output == test_input + + def test_python_version() -> None: from isort.main import parse_args |