Skip to content
10 changes: 10 additions & 0 deletions docs/configuration/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,16 @@ If `True` isort will automatically create section groups by the top-level packag
**Python & Config File Name:** group_by_package
**CLI Flags:** **Not Supported**

## Separate Packages

Separate packages within the listed sections with newlines.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind adding an example here to make it more clear?


**Type:** List of Strings
**Default:** `frozenset()`
**Config default:** `[]`
**Python & Config File Name:** separate_packages
**CLI Flags:** **Not Supported**

## Ignore Whitespace

Tells isort to ignore whitespace differences when --check-only is being used.
Expand Down
36 changes: 36 additions & 0 deletions isort/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@
from . import parse, sorting, wrap
from .comments import add_to_line as with_comments
from .identify import STATEMENT_DECLARATIONS
from .place import module_with_reason
from .settings import DEFAULT_CONFIG, Config


# Ignore DeepSource cyclomatic complexity check for this function. It was
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we dont need to add those lines (see my next comment).

# already complex when this check was enabled.
# skipcq: PY-R1000
def sorted_imports(
parsed: parse.ParsedContent,
config: Config = DEFAULT_CONFIG,
Expand Down Expand Up @@ -149,6 +153,38 @@ def sorted_imports(
section_output.append("") # Empty line for black compatibility
section_output.append(section_comment_end)

if section in config.separate_packages:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for this @alex-liang3. I know the complexy of this method was already high, but this pirce of code you added here is a perfect candidate for an extract method. With that we could start the needed refactor here to reduce the complexity. Could you please do this refactor?

group_keys: Set[str] = set()
comments_above: List[str] = []
processed_section_output: List[str] = []
for section_line in section_output:
if section_line.startswith("#"):
comments_above.append(section_line)
continue

package_name: str = section_line.split(" ")[1]
_, reason = module_with_reason(package_name, config)

if "Matched configured known pattern" in reason:
package_depth = len(reason.split(".")) - 1 # minus 1 for re.compile
key = ".".join(package_name.split(".")[: package_depth + 1])
else:
key = package_name.split(".")[0]

if key not in group_keys:
if group_keys:
processed_section_output.append("")

group_keys.add(key)

if comments_above:
processed_section_output.extend(comments_above)
comments_above = []

processed_section_output.append(section_line)

section_output = processed_section_output

if pending_lines_before or not no_lines_before:
output += [""] * config.lines_between_sections

Expand Down
1 change: 1 addition & 0 deletions isort/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ class _Config:
force_sort_within_sections: bool = False
lexicographical: bool = False
group_by_package: bool = False
separate_packages: FrozenSet[str] = frozenset()
ignore_whitespace: bool = False
no_lines_before: FrozenSet[str] = frozenset()
no_inline_sort: bool = False
Expand Down
108 changes: 108 additions & 0 deletions tests/unit/test_ticketed_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -1073,3 +1073,111 @@ def use_libc_math():
""",
show_diff=True,
)


def test_sort_separate_packages_issue_2104():
"""
Test to ensure that packages within a section can be separated by blank lines.
See: https://github.com/PyCQA/isort/issues/2104
"""

# Base case as described in issue
assert (
isort.code(
"""
import os
import sys

from django.db.models.signals import m2m_changed
from django.utils import functional
from django_filters import BooleanFilter
from junitparser import JUnitXml
from junitparser import TestSuite
from loguru import logger
""",
force_single_line=True,
separate_packages=["THIRDPARTY"],
)
== """
import os
import sys

from django.db.models.signals import m2m_changed
from django.utils import functional

from django_filters import BooleanFilter

from junitparser import JUnitXml
from junitparser import TestSuite

from loguru import logger
"""
)

# Check that multiline comments aren't broken up
assert (
isort.code(
"""
from junitparser import TestSuite
# Some multiline
# comment
from loguru import logger
""",
force_single_line=True,
separate_packages=["THIRDPARTY"],
)
== """
from junitparser import TestSuite

# Some multiline
# comment
from loguru import logger
"""
)

# Check it works for custom sections
assert (
isort.code(
"""
import os
from package2 import bar
from package1 import foo
""",
force_single_line=True,
known_MYPACKAGES=["package1", "package2"],
sections=["STDLIB", "MYPACKAGES"],
separate_packages=["MYPACKAGES"],
)
== """
import os

from package1 import foo

from package2 import bar
"""
)

# Check it works for packages with deeper nesting
assert (
isort.code(
"""
import os
from package2 import bar
from package1.a.b import foo
from package1.a.c import baz
""",
force_single_line=True,
known_MYPACKAGES=["package1.a", "package2"],
sections=["STDLIB", "MYPACKAGES"],
separate_packages=["MYPACKAGES"],
)
== """
import os

from package1.a.b import foo

from package1.a.c import baz

from package2 import bar
"""
)
Loading