Skip to content

Commit e5a27aa

Browse files
committed
implement and test module level dunders
1 parent f3bfce4 commit e5a27aa

7 files changed

Lines changed: 85 additions & 32 deletions

File tree

isort/core.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ def process(
196196
first_comment_index_end = index - 1
197197

198198
was_in_quote = bool(in_quote)
199-
if (not stripped_line.startswith("#") or in_quote) and '"' in line or "'" in line:
199+
if not is_module_dunder(stripped_line) and (not stripped_line.startswith("#") or in_quote) and ('"' in line or "'" in line):
200200
char_index = 0
201201
if first_comment_index_start == -1 and (
202202
line.startswith('"') or line.startswith("'")
@@ -290,14 +290,24 @@ def process(
290290
):
291291
import_section += line
292292
elif is_module_dunder(stripped_line):
293-
import_section += line
293+
# Handle module-level dunders.
294+
dunder_statement = line
295+
if stripped_line.endswith(("\\", "[", '= """', "= '''")):
296+
# Handle multiline module dunder assignments.
297+
while stripped_line and not stripped_line.endswith("]") and stripped_line != '"""' and stripped_line != "'''":
298+
line = input_stream.readline()
299+
stripped_line = line.strip()
300+
dunder_statement += line
301+
import_section += dunder_statement
302+
294303
elif stripped_line.startswith(IMPORT_START_IDENTIFIERS):
295304
new_indent = line[: -len(line.lstrip())]
296305
import_statement = line
297306
stripped_line = line.strip().split("#")[0]
298307
while stripped_line.endswith("\\") or (
299308
"(" in stripped_line and ")" not in stripped_line
300309
):
310+
# Handle multiline import statements.
301311
if stripped_line.endswith("\\"):
302312
while stripped_line and stripped_line.endswith("\\"):
303313
line = input_stream.readline()

isort/output.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,12 @@ def sorted_imports(
3434
parsed.imports["no_sections"] = {"straight": {}, "from": {}}
3535
base_sections: Tuple[str, ...] = ()
3636
for section in sections:
37+
if section == "DUNDER":
38+
continue
3739
if section == "FUTURE":
3840
base_sections = ("FUTURE",)
3941
continue
42+
4043
parsed.imports["no_sections"]["straight"].update(
4144
parsed.imports[section].get("straight", {})
4245
)
@@ -49,8 +52,9 @@ def sorted_imports(
4952

5053
for section in sections:
5154
if section == "DUNDER":
52-
output += [""] * config.lines_between_sections
53-
output.extend(parsed.module_dunders)
55+
if parsed.module_dunders:
56+
output += [""] * config.lines_between_sections
57+
output.extend(parsed.module_dunders)
5458
continue
5559

5660
straight_modules = parsed.imports[section]["straight"]

isort/parse.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,17 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
191191
while index < line_count:
192192
line = in_lines[index]
193193
index += 1
194+
195+
if is_module_dunder(line):
196+
dunder_statement = line
197+
if line.endswith(("\\", "[", '= """', "= '''")):
198+
while index < line_count and line and not line.endswith("]") and line != '"""' and line != "'''":
199+
line = in_lines[index]
200+
index += 1
201+
dunder_statement += "\n" + line
202+
module_dunders.append(dunder_statement)
203+
continue
204+
194205
statement_index = index
195206
(skipping_line, in_quote) = skip_line(
196207
line, in_quote=in_quote, index=index, section_comments=config.section_comments
@@ -272,11 +283,6 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte
272283
for statement in statements:
273284
line, raw_line = _normalize_line(statement)
274285
raw_lines = [raw_line]
275-
276-
if is_module_dunder(statement):
277-
module_dunders.append(statement)
278-
continue
279-
280286
type_of_import = import_type(line, config) or ""
281287

282288
if not type_of_import:

tests/unit/profiles/test_attrs.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ def test_attrs_code_snippet_one():
99
attrs_isort_test(
1010
"""from __future__ import absolute_import, division, print_function
1111
12+
__version__ = "20.2.0.dev0"
13+
1214
import sys
1315
1416
from functools import partial
@@ -28,9 +30,6 @@ def test_attrs_code_snippet_one():
2830
validate,
2931
)
3032
from ._version_info import VersionInfo
31-
32-
33-
__version__ = "20.2.0.dev0"
3433
"""
3534
)
3635

@@ -81,12 +80,6 @@ def test_attrs_code_snippet_three():
8180
8281
from __future__ import absolute_import, division, print_function
8382
84-
import re
85-
86-
from ._make import _AndValidator, and_, attrib, attrs
87-
from .exceptions import NotCallableError
88-
89-
9083
__all__ = [
9184
"and_",
9285
"deep_iterable",
@@ -98,5 +91,10 @@ def test_attrs_code_snippet_three():
9891
"optional",
9992
"provides",
10093
]
94+
95+
import re
96+
97+
from ._make import _AndValidator, and_, attrib, attrs
98+
from .exceptions import NotCallableError
10199
'''
102100
)

tests/unit/profiles/test_open_stack.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,19 @@ def test_open_stack_code_snippet_three():
100100
# License for the specific language governing permissions and limitations
101101
# under the License.
102102
103+
__all__ = [
104+
'init',
105+
'cleanup',
106+
'set_defaults',
107+
'add_extra_exmods',
108+
'clear_extra_exmods',
109+
'get_allowed_exmods',
110+
'RequestContextSerializer',
111+
'get_client',
112+
'get_server',
113+
'get_notifier',
114+
]
115+
103116
import functools
104117
105118
from oslo_log import log as logging
@@ -115,19 +128,6 @@ def test_open_stack_code_snippet_three():
115128
import nova.exception
116129
from nova.i18n import _
117130
118-
__all__ = [
119-
'init',
120-
'cleanup',
121-
'set_defaults',
122-
'add_extra_exmods',
123-
'clear_extra_exmods',
124-
'get_allowed_exmods',
125-
'RequestContextSerializer',
126-
'get_client',
127-
'get_server',
128-
'get_notifier',
129-
]
130-
131131
profiler = importutils.try_import("osprofiler.profiler")
132132
""",
133133
known_first_party=["nova"],

tests/unit/test_isort.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5593,7 +5593,7 @@ def test_infinite_loop_in_unmatched_parenthesis() -> None:
55935593
assert isort.code(test_input) == "from os import path, walk\n"
55945594

55955595

5596-
def test_dunder_imports() -> None:
5596+
def test_dunders() -> None:
55975597
"""Test to ensure dunder imports are in the correct location."""
55985598
test_input = """from __future__ import division
55995599
@@ -5616,3 +5616,37 @@ def test_dunder_imports() -> None:
56165616
"""
56175617
assert isort.code(test_input) == expected_output
56185618

5619+
def test_multiline_dunders() -> None:
5620+
"""Test to ensure isort correctly handles multiline dunders"""
5621+
test_input = """from __future__ import division
5622+
5623+
import os
5624+
import sys
5625+
5626+
__all__ = [
5627+
"one",
5628+
"two",
5629+
]
5630+
__version__ = '0.1'
5631+
__author__ = '''
5632+
first name
5633+
last name
5634+
'''
5635+
"""
5636+
5637+
expected_output = """from __future__ import division
5638+
5639+
__all__ = [
5640+
"one",
5641+
"two",
5642+
]
5643+
__version__ = '0.1'
5644+
__author__ = '''
5645+
first name
5646+
last name
5647+
'''
5648+
5649+
import os
5650+
import sys
5651+
"""
5652+
assert isort.code(test_input) == expected_output

tests/unit/test_parse.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def test_file_contents():
3333
_,
3434
_,
3535
_,
36+
_,
3637
change_count,
3738
original_line_count,
3839
_,

0 commit comments

Comments
 (0)