Skip to content

Don't require sys.stdout.buffer when writing formatted output or diffs - #5411

Merged
cobaltt7 merged 2 commits into
psf:mainfrom
ethanstoner:fix/stdout-without-buffer
Sep 21, 2026
Merged

cobaltt7 merged 2 commits into
psf:mainfrom
ethanstoner:fix/stdout-without-buffer

Conversation

@ethanstoner

Copy link
Copy Markdown
Contributor

Description

Fixes #2516

Bug: Calling black.reformat_code() / black.format_stdin_to_stdout() from a Jupyter notebook fails with AttributeError: 'OutStream' object has no attribute 'buffer'. The diff output of format_file_in_place() (WriteBack.DIFF / COLOR_DIFF) has the same problem.

Root cause: Both functions wrap sys.stdout.buffer in io.TextIOWrapper so they can control the encoding and newline translation. TextIOBase.buffer is not part of the TextIOBase API and ipykernel's OutStream doesn't have it. format_stdin_to_stdout() builds that wrapper in its finally block for every write_back mode, so even --check-style calls crash. The issue thread notes that just using getattr(sys.stdout, "buffer", sys.stdout) doesn't work either, because OutStream.write() only accepts str.

Change: Both code paths now go through a small _write_to_stdout() helper:

  • If sys.stdout has a buffer, it does what the code did before: wraps it in io.TextIOWrapper(..., write_through=True), wraps it for Windows colors where needed, writes, then detaches.
  • If there is no buffer, it writes text straight to sys.stdout. It does the \n -> newline translation itself (this is what TextIOWrapper would do, and the content has already been normalized to \n at that point) and leaves encoding to the stream. The stream is never detached or closed.

format_stdin_to_stdout() now only touches stdout when write_back is YES/DIFF/COLOR_DIFF. wrap_stream_for_windows()'s annotation is widened from io.TextIOWrapper to TextIO so it accepts sys.stdout. At runtime it already accepted any stream.

Tests: I added three tests that patch sys.stdout with an io.StringIO, which has no buffer and only accepts str (like OutStream). They cover format_stdin_to_stdout with YES/CHECK/NO/DIFF/COLOR_DIFF, reformat_code, and the format_file_in_place diff path with both \n and \r\n files. All three failed with the AttributeError before the fix and pass after it.

Validation (macOS, Python 3.14):

  • pytest -n auto tests: 484 passed, 3 skipped
  • pre-commit run -a: everything passes except mypy, which reports an action/main.py:60 tomllib error that also shows up on unmodified main in my environment. It has nothing to do with this change, and there are no mypy errors in the touched files.
  • black --check on the changed files: clean

Checklist - did you ...

  • Implement any code style changes under the --preview style, following the stability policy? (N/A, no style changes)
  • Add an entry in CHANGES.md if necessary?
  • Add / update tests if necessary?
  • Add new / update outdated documentation? (N/A)

Generated with Claude Code on behalf of @ethanstoner.

Text streams aren't required to expose their underlying binary buffer
(for example ipykernel's OutStream in Jupyter), so format_stdin_to_stdout
and the diff output of format_file_in_place crashed with AttributeError.
Fall back to writing text directly to sys.stdout, applying the newline
translation that io.TextIOWrapper would otherwise do.
@github-actions

Copy link
Copy Markdown
Contributor

diff-shades results comparing this PR (5937ae6) to main (acd6198):

--preview style: no changes

--stable style: no changes


What is this? | Workflow run | diff-shades documentation

@cobaltt7

cobaltt7 commented Sep 21, 2026 •

Copy link
Copy Markdown
Collaborator

Disregard my previous comment, the patch wasn't properly downloading. Confirmed this IS working. Thanks for the fix!

@cobaltt7
cobaltt7 merged commit da22d43 into psf:main Sep 21, 2026
57 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.

Running in Jupyter Notebook raises 'OutStream' object has no attribute 'buffer'

2 participants