Skip to content

Commit 15e5bd1

Browse files
committed
Added a fix for normalizing imports from more than one level of parent modules (issue/2152)
1 parent 9f7e0e5 commit 15e5bd1

File tree

3 files changed

+37
-9
lines changed

3 files changed

+37
-9
lines changed

isort/identify.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pathlib import Path
66
from typing import Iterator, NamedTuple, Optional, TextIO, Tuple
77

8-
from isort.parse import _normalize_line, _strip_syntax, skip_line
8+
from isort.parse import _strip_syntax, normalize_line, skip_line
99

1010
from .comments import parse as parse_comments
1111
from .settings import DEFAULT_CONFIG, Config
@@ -84,7 +84,7 @@ def imports(
8484
statements[-1] = f"{statements[-1]}#{end_of_line_comment[0]}"
8585

8686
for statement in statements:
87-
line, _raw_line = _normalize_line(statement)
87+
line, _raw_line = normalize_line(statement)
8888
if line.startswith(("import ", "cimport ")):
8989
type_of_import = "straight"
9090
elif line.startswith("from "):

isort/parse.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Defines parsing functions used by isort for parsing import definitions"""
2+
import re
23
from collections import OrderedDict, defaultdict
34
from functools import partial
45
from itertools import chain
@@ -36,18 +37,18 @@ def _infer_line_separator(contents: str) -> str:
3637
return "\n"
3738

3839

39-
def _normalize_line(raw_line: str) -> Tuple[str, str]:
40+
def normalize_line(raw_line: str) -> Tuple[str, str]:
4041
"""Normalizes import related statements in the provided line.
4142
4243
Returns (normalized_line: str, raw_line: str)
4344
"""
44-
line = raw_line.replace("from.import ", "from . import ")
45-
line = line.replace("from.cimport ", "from . cimport ")
45+
line = re.sub(r"from(\.+)cimport ", r"from \g<1> cimport ", raw_line)
46+
line = re.sub(r"from(\.+)import ", r"from \g<1> import ", line)
4647
line = line.replace("import*", "import *")
47-
line = line.replace(" .import ", " . import ")
48-
line = line.replace(" .cimport ", " . cimport ")
48+
line = re.sub(r" (\.+)import ", r" \g<1> import ", line)
49+
line = re.sub(r" (\.+)cimport ", r" \g<1> cimport ", line)
4950
line = line.replace("\t", " ")
50-
return (line, raw_line)
51+
return line, raw_line
5152

5253

5354
def import_type(line: str, config: Config = DEFAULT_CONFIG) -> Optional[str]:
@@ -263,7 +264,7 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
263264
statements[-1] = f"{statements[-1]}#{end_of_line_comment[0]}"
264265

265266
for statement in statements:
266-
line, raw_line = _normalize_line(statement)
267+
line, raw_line = normalize_line(statement)
267268
type_of_import = import_type(line, config) or ""
268269
raw_lines = [raw_line]
269270
if not type_of_import:

tests/unit/test_parse.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import pytest
12
from hypothesis import given
23
from hypothesis import strategies as st
34

@@ -81,3 +82,29 @@ def test_fuzz_skip_line(line, in_quote, index, section_comments, needs_import):
8182
section_comments=section_comments,
8283
needs_import=needs_import,
8384
)
85+
86+
87+
@pytest.mark.parametrize(
88+
"raw_line, expected",
89+
(
90+
("from . cimport a", "from . cimport a"),
91+
("from.cimport a", "from . cimport a"),
92+
("from..cimport a", "from .. cimport a"),
93+
("from . import a", "from . import a"),
94+
("from.import a", "from . import a"),
95+
("from..import a", "from .. import a"),
96+
("import *", "import *"),
97+
("import*", "import *"),
98+
("from . import a", "from . import a"),
99+
("from .import a", "from . import a"),
100+
("from ..import a", "from .. import a"),
101+
("from . cimport a", "from . cimport a"),
102+
("from .cimport a", "from . cimport a"),
103+
("from ..cimport a", "from .. cimport a"),
104+
("from\t.\timport a", "from . import a"),
105+
),
106+
)
107+
def test_normalize_line(raw_line, expected):
108+
line, returned_raw_line = parse.normalize_line(raw_line)
109+
assert line == expected
110+
assert returned_raw_line == raw_line

0 commit comments

Comments
 (0)