Skip to content

Add _io.TextIOWrapper.detach method - #6267

Merged
youknowone merged 4 commits into
RustPython:mainfrom
fanninpm:io-textiowrapper-detach
Dec 3, 2025
Merged

youknowone merged 4 commits into
RustPython:mainfrom
fanninpm:io-textiowrapper-detach

Conversation

@fanninpm

@fanninpm fanninpm commented Nov 15, 2025 •

Copy link
Copy Markdown
Contributor

As I was trying to get the regression test data uploaded to the website (it hasn't been uploaded to the website since February 2025), I ran into an issue where this method wasn't implemented. So far, I've stubbed out a dummy implementation that panics whenever this is called. Does anyone have any ideas on how to implement this method properly?

Summary by CodeRabbit

  • New Features
    • Added detach methods to text and general IO wrappers, enabling safe disconnection of underlying buffers and returning the detached buffer for reuse or transfer.
  • Bug Fixes
    • Flush behavior standardized so pending writes are reliably flushed before detach or on explicit flush, reducing data-loss risk during buffer handoff.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Nov 15, 2025 •

Copy link
Copy Markdown
Contributor

Walkthrough

Added a private flush_inner helper and a public detach(zelf, vm) method to the TextIOWrapper-like type: flush now delegates to flush_inner; detach flushes, verifies attachment, returns the underlying buffer, and clears the wrapper's buffer reference. (Under 50 words.)

Changes

Cohort / File(s) Summary
IO module — text IO detach & flush
crates/vm/src/stdlib/io.rs
Added internal helper flush_inner(textio, vm) to perform close checks, mark tellable/seekable, flush pending writes and underlying buffer. Added public detach(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult which validates not already detached, calls flush_inner, clones and returns the buffer, and clears the internal buffer reference. Updated TextIOWrapper.flush to call flush_inner instead of duplicating logic.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Verify flush ordering and error handling in flush_inner (check_closed → mark flags → flush pending → flush underlying).
  • Confirm detach prevents double-detach and that buffer cloning/return semantics are correct.
  • Inspect that clearing the internal buffer reference avoids subsequent use-after-detach and that flush now consistently delegates.
  • Check for any import adjustments or lint warnings in the modified file.

Poem

🐰 I nudged at the stream, gave a gentle shove,
I scrubbed every nibble, then sent it above;
I loosened the clasp, handed back the small cup,
Cleared my soft paws, then I hopped off — up and up! 🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately describes the main change: adding the detach method to _io.TextIOWrapper. It is concise, specific, and directly reflects the primary objective of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 034de19 and 4b6935d.

📒 Files selected for processing (1)
  • crates/vm/src/stdlib/io.rs (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.rs: Follow the default rustfmt code style by running cargo fmt to format Rust code
Always run clippy to lint Rust code (cargo clippy) before completing tasks and fix any warnings or lints introduced by changes
Follow Rust best practices for error handling and memory management
Use the macro system (pyclass, pymodule, pyfunction, etc.) when implementing Python functionality in Rust

Files:

  • crates/vm/src/stdlib/io.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Run snippets and cpython tests (windows-latest)
  • GitHub Check: Run snippets and cpython tests on wasm-wasi
  • GitHub Check: Check the WASM package and demo
  • GitHub Check: Run snippets and cpython tests (ubuntu-latest)
  • GitHub Check: Run snippets and cpython tests (macos-latest)
  • GitHub Check: Run tests under miri
  • GitHub Check: Run rust tests (windows-latest)
  • GitHub Check: Run rust tests (ubuntu-latest)
  • GitHub Check: Run rust tests (macos-latest)
  • GitHub Check: Check Rust code with clippy
  • GitHub Check: Ensure compilation on various targets
🔇 Additional comments (3)
crates/vm/src/stdlib/io.rs (3)

2408-2414: LGTM! Clean helper implementation.

The flush_inner helper correctly extracts the flush logic as requested by the maintainer. It takes &mut TextIOData directly, avoiding the re-entrancy issues that plagued the initial detach implementation. The logic flow (check closed → update telling → write pending → flush buffer) is appropriate.


2450-2464: Excellent implementation of detach!

The detach method correctly addresses all the critical issues raised in previous reviews:

  • No re-entrancy: Uses flush_inner(&mut textio, vm) instead of calling vm.call_method(zelf.as_object(), "flush", ...), preventing the deadlock
  • Detached state check: Properly checks vm.is_none(&textio.buffer) upfront and returns a clear error message
  • Correct sequencing: Flushes while still attached, then detaches and returns the buffer

The logic flow ensures that if flush_inner fails, the buffer remains attached (which is the correct behavior).

Note: As mentioned in earlier discussions, other methods like seekable, readable, writable may still produce AttributeError after detach rather than a clear "buffer is detached" message. Adding a check_attached helper and calling it from those methods would be a nice follow-up enhancement, but that was acknowledged as optional.


2898-2901: LGTM! Good refactoring.

The flush method now correctly delegates to flush_inner, eliminating code duplication and ensuring consistent behavior between flush and detach.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@fanninpm

Copy link
Copy Markdown
Contributor Author

Yes, the CI failures are expected at this point. test_io will almost certainly crash due to the aforementioned todo!().

@youknowone

Copy link
Copy Markdown
Member

Checking CPython _io_TextIOWrapper_detach_impl will give hints

@fanninpm
fanninpm force-pushed the io-textiowrapper-detach branch from 051efb1 to 05c183f Compare December 2, 2025 15:47
@fanninpm
fanninpm marked this pull request as ready for review December 2, 2025 16:14

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/vm/src/stdlib/io.rs (1)

122-124: Prefer vm.ctx.none() over importing PyNone just for assignment

PyNone appears to be imported solely to set textio.buffer to None in TextIOWrapper.detach. That can usually be done via the context singleton (vm.ctx.none()), avoiding the extra builtins import and staying consistent with the rest of this module.

If you adopt the vm.ctx.none() pattern in detach, you can drop PyNone from the import list here.

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d287d1e and 05c183f.

📒 Files selected for processing (1)
  • crates/vm/src/stdlib/io.rs (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.rs: Follow the default rustfmt code style by running cargo fmt to format Rust code
Always run clippy to lint Rust code (cargo clippy) before completing tasks and fix any warnings or lints introduced by changes
Follow Rust best practices for error handling and memory management
Use the macro system (pyclass, pymodule, pyfunction, etc.) when implementing Python functionality in Rust

Files:

  • crates/vm/src/stdlib/io.rs

Comment thread crates/vm/src/stdlib/io.rs Resolved

@youknowone youknowone 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.

Thanks! It is looking good and the missing detach seems blocking many tests.
I left a suggestion to refactor flush part

Comment thread crates/vm/src/stdlib/io.rs Outdated
Comment on lines +2452 to +2455
textio.check_closed(vm)?;
textio.telling = textio.seekable;
textio.write_pending(vm)?;
vm.call_method(&textio.buffer, "flush", ())?;

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.

Could you create fn flush_inner(textio: ..., vm: ...) -> PyResult to share the actual flushing between methods?

@coderabbitai please advice about the nominal type of textio

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@youknowone The nominal type for textio in the flush_inner helper function should be &mut TextIOData.

Looking at the code, both flush and detach methods call self.lock(vm)? which returns PyMappedThreadMutexGuard<'_, TextIOData>. This guard derefs to TextIOData, so you can pass it as a mutable reference to the helper function.

Here's the suggested signature:

fn flush_inner(textio: &mut TextIOData, vm: &VirtualMachine) -> PyResult {
    textio.check_closed(vm)?;
    textio.telling = textio.seekable;
    textio.write_pending(vm)?;
    vm.call_method(&textio.buffer, "flush", ())
}

Then both methods can call it like:

#[pymethod]
fn flush(&self, vm: &VirtualMachine) -> PyResult {
    let mut textio = self.lock(vm)?;
    flush_inner(&mut textio, vm)
}

#[pymethod]
fn detach(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
    let mut textio = zelf.lock(vm)?;
    
    // Fail fast if already detached
    if vm.is_none(&textio.buffer) {
        return Err(vm.new_value_error("underlying buffer has been detached"));
    }
    
    flush_inner(&mut textio, vm)?;
    
    let buffer = textio.buffer.clone();
    textio.buffer = vm.ctx.none();
    Ok(buffer)
}

This approach cleanly separates the locking concern from the flushing logic and avoids code duplication.

@youknowone youknowone 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.

👍 Thank you!

@youknowone
youknowone merged commit 59f422d into RustPython:main Dec 3, 2025
13 checks passed
@fanninpm
fanninpm deleted the io-textiowrapper-detach branch December 4, 2025 02:34
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