Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions docs/configuration/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,59 @@ 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**

**Examples:**

### Example `.isort.cfg`

```
[settings]
separate_packages=THIRDPARTY
```

### Example `pyproject.toml`

```
[tool.isort]
separate_packages = ["THIRDPARTY"]
```

### Example before:
```python
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 loguru import logger
```

### Example after:
```python
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 loguru import logger
```

## Ignore Whitespace

Tells isort to ignore whitespace differences when --check-only is being used.
Expand Down
38 changes: 38 additions & 0 deletions isort/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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


Expand Down Expand Up @@ -149,6 +150,9 @@ 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?

section_output = _separate_packages(section_output, config)

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

Expand Down Expand Up @@ -674,3 +678,37 @@ def _with_star_comments(parsed: parse.ParsedContent, module: str, comments: List
if star_comment:
return [*comments, star_comment]
return comments


def _separate_packages(section_output: List[str], config: Config) -> List[str]:
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)

return processed_section_output
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
"""
)