Skip to content

Fix Windows stdin/stdout newline preservation - #2603

Closed
shleder wants to merge 1 commit into
PyCQA:mainfrom
shleder:fix/windows-stdin-stdout-newlines
Closed

Fix Windows stdin/stdout newline preservation#2603
shleder wants to merge 1 commit into
PyCQA:mainfrom
shleder:fix/windows-stdin-stdout-newlines

Conversation

@shleder

@shleder shleder commented Jul 28, 2026

Copy link
Copy Markdown

Problem

When isort processes source through stdin on Windows, the default TextIOWrapper newline translation can convert LF input to CRLF output or introduce duplicated carriage returns. This affects normal sorting, skipped-file passthrough, check mode, and diff output.

Root cause

The CLI streamed through sys.stdin and sys.stdout without disabling platform newline translation. On Windows, the standard text streams could therefore translate newlines independently of isort's own line-ending handling.

Implementation

  • Reconfigure sys.stdout with newline="" before processing stdin.
  • Reconfigure sys.stdin with newline="" only when the CLI uses the real standard input.
  • Never reconfigure an explicitly injected stdin stream.
  • Gracefully tolerate streams that do not support reconfigure().
  • Narrow the standard streams to TextIOWrapper with cast() for strict mypy compatibility.
  • Add an Unreleased changelog entry.

Regression coverage

The new regression test verifies exact LF output, exact CRLF output, non-ASCII content preservation, LF and CRLF isort: skip_file passthrough, sorted and unsorted --check-only behavior, LF diff output without introduced carriage returns, CRLF diff output without duplicated carriage returns, injected stdin streams remaining untouched, and streams without reconfigure() completing without a crash.

Validation

The patch was applied to a pristine checkout of base commit 47c4f9819c3e92d296efe43c2f460e6742a1a75f and compared with an independent pristine checkout of the same commit.

  • The focused regression passes on the patched checkout. The test does not exist on the base checkout.
  • The patched checkout adds no new full-suite failures. The same two failures occur on the pristine base and patched checkouts: tests/unit/test_importable.py::test_importable and tests/unit/test_regressions.py::test_windows_diff_too_large_misrepresentative_issue_1348.
  • Full strict mypy produces the same pre-existing Windows diagnostic on base and patched: tests/unit/test_settings.py:86: error: Module has no attribute "mkfifo" [attr-defined].
  • Ruff check passes on base and patched.
  • Ruff format check passes on base and patched.
  • Configured flake8 passes on base and patched.
  • git diff --check passes for the patch.
  • The full patched unit run reports 606 passed, 5 skipped, and the same 2 baseline failures. The full base unit run reports 605 passed, 5 skipped, and those same 2 failures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.43%. Comparing base (47c4f98) to head (e27b5c1).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2603      +/-   ##
==========================================
+ Coverage   99.33%   99.43%   +0.09%     
==========================================
  Files          41       41              
  Lines        3157     3158       +1     
  Branches      682      683       +1     
==========================================
+ Hits         3136     3140       +4     
+ Misses         14       11       -3     
  Partials        7        7              
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@DanielNoord DanielNoord left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think we should be reconfguring sys.stdin. main might be called programatically by other Python programs that do not want a reconfigured sys.stdin.

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.

2 participants