Skip to content

Sort an indented import block that is followed by a less indented import - #2730

Merged
DanielNoord merged 2 commits into
PyCQA:mainfrom
ybai08:issue/2010
Oct 21, 2026
Merged

DanielNoord merged 2 commits into
PyCQA:mainfrom
ybai08:issue/2010

Conversation

@ybai08

@ybai08 ybai08 commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

Fixes #2010

This is the second part of #2010 that @Eljees described: the else: imports are not combined, even without lines_before_imports.

import sys

if sys.version_info >= (3, 11):
    from typing import LiteralString, Never
else:
    from typing_extensions import LiteralString
    from typing_extensions import Never

import third_party_library

On main, isort leaves the two from typing_extensions import ... lines as they are. If you remove the last line, they are combined.

Cause

When an import with less indentation ends an indented import block, process() sets indent to the new indentation before it sorts the block it has collected so far. So that block is sorted as if it were not indented, and it comes out unchanged.

Change

  • The new indentation is stored in next_indent and applied once the current block has been written. This only happens when the block already holds imports. When the block holds only comments or blank lines, indent still switches right away, as before.
  • That block is now sorted as an indented section. Its output is the same as when code follows the block. For example, def f():\n import os\n import a\n import os\n sorts to the same lines whether x = 1 or import b comes next. The two places that used to read indent after the early switch now read the next section's indentation:
    • the separator line written before a following top-level section (lines_before_imports == -1);
    • the trailing blank lines of the block. They are left to the next section, which already writes its own blank lines (or exactly lines_before_imports of them).

These are the four tests @Eljees mentioned (test_nested_comment_handling, test_autofix_mixed_indent_imports_1575, test_unindented_comment_in_indented_block_issue_1899, test_isort_should_leave_non_import_from_lines_alone_issue_1488), plus test_lines_before_imports_keeps_import_after_indented_block. All of them pass unchanged.

Tests

New test_indented_imports_followed_by_less_indented_import_issue_2010 in tests/unit/test_regressions.py. It covers:

  • the example from the issue;
  • a function body followed by a top-level import, with and without lines_before_imports;
  • a dedent from two levels to one.

Each case also checks that the output is stable on a second run. The test fails on main and passes with the change.

What was run (Python 3.14, uv sync --all-extras --frozen):

  • pytest tests/unit: 695 passed, 1 skipped.
  • pytest tests/integration/test_hypothesmith.py tests/integration/test_literal.py tests/integration/test_setting_combinations.py tests/integration/test_ticketed_features.py: 3 passed.
  • The tox -e lint commands, run one by one: mypy, isort --profile hug --check --diff isort/ tests/ scripts/, flake8 isort/ tests/, ruff check, ruff format --check, bandit -r isort/. All clean.
  • A differential check: 9,000 random snippets of nested blocks with imports, comments and code, under seven option sets, run through main and through this branch.
    • The output differs on 535 of them. Every one has an indented import followed by a less indented import.
    • On every snippet that parses, the set of imported names is unchanged.
    • The new output is stable on a second run except in one case. main gives the same second-run result there: with lines_before_imports=1, it adds a blank line between # comment and the import below it.

Not run: tests/integration/test_projects_using_isort.py, which clones other projects.

When an import at a smaller indentation ended an indented import block,
`process()` switched `indent` to the new indentation before the block
collected so far was sorted. The block was then handled as if it were not
indented and was left as it was, so for example the two
`from typing_extensions import ...` lines of an `else:` branch were not
combined when a top-level import followed. The switch now waits until the
block has been written, and the block is sorted the same way as when code
follows it.

Fixes PyCQA#2010

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Partial dedents discard existing blank-line separators that the following indented section does not restore.

1 open issue
What changed in this PR

Fixes the sorting issue in #2010 by preserving an import block’s indentation until sorting is complete.

Changes:

  • Defers indentation changes until the next import section.
  • Adjusts spacing between sections.
  • Adds regression tests for top-level and nested dedents, including output stability.
File Description
tests/​unit/​test_regressions.py Adds coverage for indented imports followed by less-indented imports.
isort/​core.py Preserves indentation during sorting and adjusts section spacing.

Review effort: Balanced

Comment thread isort/core.py Outdated
Keep existing separators when an indented import block is followed by an import at a less-indented, but still nested, level.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Approved

The focused indentation fix has regression coverage and no unresolved findings.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

@DanielNoord
DanielNoord enabled auto-merge October 21, 2026 20:53
@codecov

codecov Bot commented Oct 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.38%. Comparing base (5a6aaf5) to head (9fdf9d2).
⚠️ Report is 20 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2730   +/-   ##
=======================================
  Coverage   99.37%   99.38%           
=======================================
  Files          40       40           
  Lines        3224     3272   +48     
  Branches      689      706   +17     
=======================================
+ Hits         3204     3252   +48     
  Misses         12       12           
  Partials        8        8           
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@DanielNoord
DanielNoord disabled auto-merge October 21, 2026 21:37
@DanielNoord
DanielNoord added this pull request to the merge queue Oct 21, 2026
Merged via the queue into PyCQA:main with commit b6f2dc5 Oct 21, 2026
67 of 69 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Formatting problems in an else conditional import

3 participants