Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable writing to python stdio streams #3920

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions newsfragments/3920.added.md
@@ -0,0 +1 @@
Add `pyo3::stdio::stdout` and `pyo3::stdio::stderr` to enable direct print to python `sys.stdout` and `sys.stderr`.
1 change: 1 addition & 0 deletions src/lib.rs
Expand Up @@ -430,6 +430,7 @@ pub mod impl_;
mod instance;
pub mod marker;
pub mod marshal;
pub mod stdio;
#[macro_use]
pub mod sync;
pub mod panic;
Expand Down
93 changes: 93 additions & 0 deletions src/stdio.rs
@@ -0,0 +1,93 @@
//! Enables direct write access to I/O streams in Python's `sys` module.

//! In some cases printing to Rust's `std::io::stdout` or `std::io::stderr` will not appear
//! in the Python interpreter, e.g. in Jupyter notebooks. This module provides a way to write
//! directly to Python's I/O streams from Rust in such cases.

//! ```rust
//! use std::io::Write;
//!
//! let mut stdout = pyo3::stdio::stdout();
//!
//! // This may not appear in Jupyter notebooks...
//! println!("Hello, world!");
//!
//! // ...but this will.
//! writeln!(stdout, "Hello, world!").unwrap();
//! ```

use crate::ffi::{PySys_WriteStderr, PySys_WriteStdout};
use crate::Python;
use std::io::{LineWriter, Write};
use std::os::raw::c_char;

macro_rules! make_python_stdio {
adamreichold marked this conversation as resolved.
Show resolved Hide resolved
($rawtypename:ident, $typename:ident, $pyfunc:ident, $stdio:ident) => {
struct $rawtypename {
cbuffer: Vec<u8>,
}
impl $rawtypename {
fn new() -> Self {
Self {
cbuffer: Vec::new(),
}
}
}
impl Write for $rawtypename {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
//clear internal buffer and then overwrite with the
//new buffer and a null terminator
self.cbuffer.clear();
self.cbuffer.extend_from_slice(buf);
self.cbuffer.push(0);
Python::with_gil(|_py| unsafe {
$pyfunc(self.cbuffer.as_ptr() as *const c_char);
adamreichold marked this conversation as resolved.
Show resolved Hide resolved
});
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
// call the python flush() on sys.$pymodname
Python::with_gil(|py| -> std::io::Result<()> {
py.run_bound(
std::concat!("import sys; sys.", stringify!($stdio), ".flush()"),
adamreichold marked this conversation as resolved.
Show resolved Hide resolved
None,
None,
)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
Ok(())
})
}
}

#[doc=std::concat!("A handle to Python's `sys.", stringify!($stdio),"` stream.")]
pub struct $typename {
inner: LineWriter<$rawtypename>,
}

impl $typename {
fn new() -> Self {
Self {
inner: LineWriter::new($rawtypename::new()),
}
}
}

impl Write for $typename {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.inner.flush()
}
}

#[doc=std::concat!("Construct a new handle to Python's `sys.", stringify!($stdio),"` stream.")]
pub fn $stdio() -> $typename {
$typename::new()
}

};

}
make_python_stdio!(PyStdoutRaw, PyStdout, PySys_WriteStdout, stdout);
make_python_stdio!(PyStderrRaw, PyStderr, PySys_WriteStderr, stderr);