From d360b44f4363f3f624986ca1f7c8fa22294b6fd8 Mon Sep 17 00:00:00 2001 From: goulart-paul Date: Fri, 1 Mar 2024 15:12:50 +0000 Subject: [PATCH 01/14] Enable writing to python stdio streams --- src/lib.rs | 1 + src/stdio.rs | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 src/stdio.rs diff --git a/src/lib.rs b/src/lib.rs index c2591cec91e..934483aad33 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/stdio.rs b/src/stdio.rs new file mode 100644 index 00000000000..6e5ad0d0420 --- /dev/null +++ b/src/stdio.rs @@ -0,0 +1,92 @@ +//! 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 +//! 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::prelude::*; +use std::io::{LineWriter, Write}; +use std::os::raw::c_char; + +macro_rules! make_python_stdio { + ($rawtypename:ident, $typename:ident, $pyfunc:ident, $stdio:ident) => { + struct $rawtypename { + cbuffer: Vec, + } + impl $rawtypename { + fn new() -> Self { + Self { + cbuffer: Vec::new(), + } + } + } + impl Write for $rawtypename { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + //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); + }); + 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()"), + 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 { + 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); + From b0217de45788f425b79bb907d95d0d4785f43b55 Mon Sep 17 00:00:00 2001 From: goulart-paul Date: Fri, 1 Mar 2024 15:53:44 +0000 Subject: [PATCH 02/14] add newsfragment --- newsfragments/3920.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/3920.added.md diff --git a/newsfragments/3920.added.md b/newsfragments/3920.added.md new file mode 100644 index 00000000000..f90e92fdfff --- /dev/null +++ b/newsfragments/3920.added.md @@ -0,0 +1 @@ +Add `Py::drop_ref` to explicitly drop a `Py`` and immediately decrease the Python reference count if the GIL is already held. From 19bb49220762817a076f7ee4041413d72cb2ea0b Mon Sep 17 00:00:00 2001 From: goulart-paul Date: Fri, 1 Mar 2024 15:57:03 +0000 Subject: [PATCH 03/14] add newsfragment --- newsfragments/3920.added.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/newsfragments/3920.added.md b/newsfragments/3920.added.md index f90e92fdfff..e19b0eb0e77 100644 --- a/newsfragments/3920.added.md +++ b/newsfragments/3920.added.md @@ -1 +1 @@ -Add `Py::drop_ref` to explicitly drop a `Py`` and immediately decrease the Python reference count if the GIL is already held. +Add `pyo3::stdio::stdout` and `pyo3::stdio::stderr` to enable direct print to python `sys.stdout` and `sys.stderr`. \ No newline at end of file From 421fb0374502c36167e89033c42fc40e8c9ee453 Mon Sep 17 00:00:00 2001 From: goulart-paul Date: Fri, 1 Mar 2024 19:51:49 +0000 Subject: [PATCH 04/14] rustfmt --- src/stdio.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/stdio.rs b/src/stdio.rs index 6e5ad0d0420..c80398f387d 100644 --- a/src/stdio.rs +++ b/src/stdio.rs @@ -1,7 +1,7 @@ //! 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 +//! 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 @@ -57,7 +57,7 @@ macro_rules! make_python_stdio { } } - #[doc=std::concat!("A handle to Python's `sys.", stringify!($stdio),"` stream.")] + #[doc=std::concat!("A handle to Python's `sys.", stringify!($stdio),"` stream.")] pub struct $typename { inner: LineWriter<$rawtypename>, } @@ -79,14 +79,13 @@ macro_rules! make_python_stdio { } } - #[doc=std::concat!("Construct a new handle to Python's `sys.", stringify!($stdio),"` stream.")] + #[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); - From de7d4de351d8d3627eec12b7da0629cf3b2ca487 Mon Sep 17 00:00:00 2001 From: goulart-paul Date: Fri, 1 Mar 2024 22:25:35 +0000 Subject: [PATCH 05/14] add missing doc example imports --- src/stdio.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/stdio.rs b/src/stdio.rs index c80398f387d..4ab5896c92d 100644 --- a/src/stdio.rs +++ b/src/stdio.rs @@ -5,6 +5,8 @@ //! 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... @@ -14,8 +16,8 @@ //! writeln!(stdout, "Hello, world!").unwrap(); //! ``` +use crate::Python; use crate::ffi::{PySys_WriteStderr, PySys_WriteStdout}; -use crate::prelude::*; use std::io::{LineWriter, Write}; use std::os::raw::c_char; From 7e0d188433029750d95e9d9bc19ca258def301b0 Mon Sep 17 00:00:00 2001 From: goulart-paul Date: Fri, 1 Mar 2024 22:27:33 +0000 Subject: [PATCH 06/14] rustfmt --- src/stdio.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stdio.rs b/src/stdio.rs index 4ab5896c92d..c5578b8ffd5 100644 --- a/src/stdio.rs +++ b/src/stdio.rs @@ -16,8 +16,8 @@ //! writeln!(stdout, "Hello, world!").unwrap(); //! ``` -use crate::Python; use crate::ffi::{PySys_WriteStderr, PySys_WriteStdout}; +use crate::Python; use std::io::{LineWriter, Write}; use std::os::raw::c_char; From 7a8f468433ec70b6a3f25893b9ecc862ce1e4efc Mon Sep 17 00:00:00 2001 From: goulart-paul Date: Sun, 3 Mar 2024 10:09:03 +0000 Subject: [PATCH 07/14] rewrite w/o macros --- src/stdio.rs | 285 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 220 insertions(+), 65 deletions(-) diff --git a/src/stdio.rs b/src/stdio.rs index c5578b8ffd5..4f3ede404a2 100644 --- a/src/stdio.rs +++ b/src/stdio.rs @@ -5,8 +5,6 @@ //! 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... @@ -17,77 +15,234 @@ //! ``` use crate::ffi::{PySys_WriteStderr, PySys_WriteStdout}; -use crate::Python; +use crate::prelude::*; use std::io::{LineWriter, Write}; -use std::os::raw::c_char; +use std::marker::PhantomData; +use std::os::raw::{c_char, c_int}; -macro_rules! make_python_stdio { - ($rawtypename:ident, $typename:ident, $pyfunc:ident, $stdio:ident) => { - struct $rawtypename { - cbuffer: Vec, - } - impl $rawtypename { - fn new() -> Self { - Self { - cbuffer: Vec::new(), - } - } - } - impl Write for $rawtypename { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - //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); - }); - 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()"), - None, - None, - ) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; - Ok(()) - }) - } - } +trait PyStdioRawConfig { + const STREAM: &'static str; + const PRINTFCN: unsafe extern "C" fn(*const i8, ...); +} - #[doc=std::concat!("A handle to Python's `sys.", stringify!($stdio),"` stream.")] - pub struct $typename { - inner: LineWriter<$rawtypename>, - } +struct PyStdoutRaw {} +impl PyStdioRawConfig for PyStdoutRaw { + const STREAM: &'static str = "stdout"; + const PRINTFCN: unsafe extern "C" fn(*const i8, ...) = PySys_WriteStdout; +} - impl $typename { - fn new() -> Self { - Self { - inner: LineWriter::new($rawtypename::new()), - } - } - } +struct PyStderrRaw {} +impl PyStdioRawConfig for PyStderrRaw { + const STREAM: &'static str = "stderr"; + const PRINTFCN: unsafe extern "C" fn(*const i8, ...) = PySys_WriteStderr; +} - impl Write for $typename { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.inner.write(buf) - } - fn flush(&mut self) -> std::io::Result<()> { - self.inner.flush() - } +struct PyStdioRaw { + pystream: Py, + _phantom: PhantomData, +} + +impl PyStdioRaw { + fn new() -> Self { + let pystream: Py = Python::with_gil(|py| { + let module = PyModule::import_bound(py, "sys").unwrap(); + module.getattr(T::STREAM).unwrap().into() + }); + + Self { + pystream, + _phantom: PhantomData, } + } +} + +impl Write for PyStdioRaw { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + Python::with_gil(|_py| unsafe { + (T::PRINTFCN)( + b"%.*s\0".as_ptr().cast(), + buf.len() as c_int, + buf.as_ptr() as *const c_char, + ); + }); + Ok(buf.len()) + } + fn flush(&mut self) -> std::io::Result<()> { + Python::with_gil(|py| -> std::io::Result<()> { + self.pystream + .call_method0(py, "flush") + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + Ok(()) + }) + } +} - #[doc=std::concat!("Construct a new handle to Python's `sys.", stringify!($stdio),"` stream.")] - pub fn $stdio() -> $typename { - $typename::new() + +struct PyStdio { + inner: LineWriter>, +} + +impl PyStdio { + fn new() -> Self { + Self { + inner: LineWriter::new(PyStdioRaw::new()), } + } +} - }; +impl Write for PyStdio { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.inner.write(buf) + } + fn flush(&mut self) -> std::io::Result<()> { + self.inner.flush() + } +} + +/// A handle to Python's `sys.stdout` stream. +pub struct PyStdout(PyStdio); +/// A handle to Python's `sys.stderr` stream. +pub struct PyStderr(PyStdio); + +/// Construct a new handle to Python's `sys.stdout` stream. +pub fn stdout() -> PyStdout { + PyStdout(PyStdio::new()) +} +/// Construct a new handle to Python's `sys.stderr` stream. +pub fn stderr() -> PyStderr { + PyStderr(PyStdio::new()) +} +impl Write for PyStdout { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.0.write(buf) + } + fn flush(&mut self) -> std::io::Result<()> { + self.0.flush() + } } -make_python_stdio!(PyStdoutRaw, PyStdout, PySys_WriteStdout, stdout); -make_python_stdio!(PyStderrRaw, PyStderr, PySys_WriteStderr, stderr); +impl Write for PyStderr { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.0.write(buf) + } + fn flush(&mut self) -> std::io::Result<()> { + self.0.flush() + } +} + +// macro_rules! make_python_stdio { +// ($rawtypename:ident, $typename:ident, $pyfunc:ident, $stdio:ident) => { +// struct $rawtypename { +// cbuffer: Vec, +// } +// impl $rawtypename { +// fn new() -> Self { +// Self { +// cbuffer: Vec::new(), +// } +// } +// } +// impl Write for $rawtypename { +// fn write(&mut self, buf: &[u8]) -> std::io::Result { +// //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 { +// PySys_WriteStdout(b"%.*s\n\0".as_ptr().cast(), buf.len() as c_int, buf.as_ptr() as *const c_char); +// PySys_FormatStdout(b"%.*d\n\0".as_ptr().cast(), 10,256137); +// }); +// 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()"), +// 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 { +// 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); + +// /// A handle to Python's `sys.stdout` stream. +// pub struct PyStdout {} +// /// A handle to Python's `sys.stderr` stream. +// pub struct PyStderr {} + +// /// Construct a new handle to Python's `sys.stdout` stream. +// pub fn stdout() -> PyStdout { +// PyStdout{} +// } +// /// Construct a new handle to Python's `sys.stderr` stream. +// pub fn stderr() -> PyStderr { +// PyStderr{} +// } + +// macro_rules! make_python_stdio { +// ($typename:ident, $printfcn:ident) => { + +// impl Write for $typename { +// fn write(&mut self, buf: &[u8]) -> std::io::Result { +// //clear internal buffer and then overwrite with the +// //new buffer and a null terminator +// Python::with_gil(|_py| unsafe { +// PySys_WriteStdout(b"%.*s\0".as_ptr().cast(), buf.len() as c_int, buf.as_ptr() as *const c_char); +// }); +// 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( +// "import sys; sys.stdout.flush()", +// None, +// None, +// ) +// .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; +// Ok(()) +// }) +// } +// } + +// }; +// } +// make_python_stdio!(PyStdout, PySys_FormatStdout); +// make_python_stdio!(PyStderr, PySys_WriteStderr); From 0d5e21db76c77a1c48d3d1620363e87d9dc86532 Mon Sep 17 00:00:00 2001 From: goulart-paul Date: Sun, 3 Mar 2024 10:09:47 +0000 Subject: [PATCH 08/14] remove old comments --- src/stdio.rs | 117 --------------------------------------------------- 1 file changed, 117 deletions(-) diff --git a/src/stdio.rs b/src/stdio.rs index 4f3ede404a2..724d854ce3f 100644 --- a/src/stdio.rs +++ b/src/stdio.rs @@ -129,120 +129,3 @@ impl Write for PyStderr { self.0.flush() } } - -// macro_rules! make_python_stdio { -// ($rawtypename:ident, $typename:ident, $pyfunc:ident, $stdio:ident) => { -// struct $rawtypename { -// cbuffer: Vec, -// } -// impl $rawtypename { -// fn new() -> Self { -// Self { -// cbuffer: Vec::new(), -// } -// } -// } -// impl Write for $rawtypename { -// fn write(&mut self, buf: &[u8]) -> std::io::Result { -// //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 { -// PySys_WriteStdout(b"%.*s\n\0".as_ptr().cast(), buf.len() as c_int, buf.as_ptr() as *const c_char); -// PySys_FormatStdout(b"%.*d\n\0".as_ptr().cast(), 10,256137); -// }); -// 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()"), -// 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 { -// 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); - -// /// A handle to Python's `sys.stdout` stream. -// pub struct PyStdout {} -// /// A handle to Python's `sys.stderr` stream. -// pub struct PyStderr {} - -// /// Construct a new handle to Python's `sys.stdout` stream. -// pub fn stdout() -> PyStdout { -// PyStdout{} -// } -// /// Construct a new handle to Python's `sys.stderr` stream. -// pub fn stderr() -> PyStderr { -// PyStderr{} -// } - -// macro_rules! make_python_stdio { -// ($typename:ident, $printfcn:ident) => { - -// impl Write for $typename { -// fn write(&mut self, buf: &[u8]) -> std::io::Result { -// //clear internal buffer and then overwrite with the -// //new buffer and a null terminator -// Python::with_gil(|_py| unsafe { -// PySys_WriteStdout(b"%.*s\0".as_ptr().cast(), buf.len() as c_int, buf.as_ptr() as *const c_char); -// }); -// 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( -// "import sys; sys.stdout.flush()", -// None, -// None, -// ) -// .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; -// Ok(()) -// }) -// } -// } - -// }; -// } -// make_python_stdio!(PyStdout, PySys_FormatStdout); -// make_python_stdio!(PyStderr, PySys_WriteStderr); From 5ad567b5c709d4bd9f8baaee08e3301199297fc9 Mon Sep 17 00:00:00 2001 From: goulart-paul Date: Sun, 3 Mar 2024 10:38:05 +0000 Subject: [PATCH 09/14] intern! flush --- src/stdio.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/stdio.rs b/src/stdio.rs index 724d854ce3f..3a7c3641aab 100644 --- a/src/stdio.rs +++ b/src/stdio.rs @@ -15,6 +15,7 @@ //! ``` use crate::ffi::{PySys_WriteStderr, PySys_WriteStdout}; +use crate::intern; use crate::prelude::*; use std::io::{LineWriter, Write}; use std::marker::PhantomData; @@ -70,7 +71,7 @@ impl Write for PyStdioRaw { fn flush(&mut self) -> std::io::Result<()> { Python::with_gil(|py| -> std::io::Result<()> { self.pystream - .call_method0(py, "flush") + .call_method0(py, intern!(py, "flush")) .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; Ok(()) }) From 6fa04aabcbfdebd7bfa29824ecc00a57b6fb7d11 Mon Sep 17 00:00:00 2001 From: goulart-paul Date: Sun, 3 Mar 2024 10:42:23 +0000 Subject: [PATCH 10/14] rustfmt --- src/stdio.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/stdio.rs b/src/stdio.rs index 3a7c3641aab..a43dffc1106 100644 --- a/src/stdio.rs +++ b/src/stdio.rs @@ -78,7 +78,6 @@ impl Write for PyStdioRaw { } } - struct PyStdio { inner: LineWriter>, } From 41d600aeda0a64bce6871848fbdb9ad729a2bba7 Mon Sep 17 00:00:00 2001 From: goulart-paul Date: Mon, 4 Mar 2024 19:14:14 +0000 Subject: [PATCH 11/14] rewrite using PyWriter(Py) --- src/stdio.rs | 124 +++++++++++++-------------------------------------- 1 file changed, 32 insertions(+), 92 deletions(-) diff --git a/src/stdio.rs b/src/stdio.rs index a43dffc1106..6e4e8fc53e0 100644 --- a/src/stdio.rs +++ b/src/stdio.rs @@ -14,118 +14,58 @@ //! writeln!(stdout, "Hello, world!").unwrap(); //! ``` -use crate::ffi::{PySys_WriteStderr, PySys_WriteStdout}; +use crate::types::PyString; use crate::intern; use crate::prelude::*; use std::io::{LineWriter, Write}; -use std::marker::PhantomData; -use std::os::raw::{c_char, c_int}; -trait PyStdioRawConfig { - const STREAM: &'static str; - const PRINTFCN: unsafe extern "C" fn(*const i8, ...); -} +pub struct PyWriter(Py); -struct PyStdoutRaw {} -impl PyStdioRawConfig for PyStdoutRaw { - const STREAM: &'static str = "stdout"; - const PRINTFCN: unsafe extern "C" fn(*const i8, ...) = PySys_WriteStdout; +fn get_stdio_writer(stream: &str) -> PyWriter { + Python::with_gil(|py| { + let module = PyModule::import_bound(py, "sys").unwrap(); + module.getattr(stream).unwrap(); + PyWriter(module.into()) + }) } -struct PyStderrRaw {} -impl PyStdioRawConfig for PyStderrRaw { - const STREAM: &'static str = "stderr"; - const PRINTFCN: unsafe extern "C" fn(*const i8, ...) = PySys_WriteStderr; +/// Construct a new handle to Python's `sys.stdout` stream. +pub fn stdout() -> PyWriter { + get_stdio_writer("stdout") } -struct PyStdioRaw { - pystream: Py, - _phantom: PhantomData, +/// Construct a new handle to Python's `sys.stderr` stream. +pub fn stderr() -> PyWriter { + get_stdio_writer("stderr") +} + +/// Construct a new handle to Python's `sys.__stdout__` stream. +pub fn __stdout__() -> PyWriter { + get_stdio_writer("__stdout__") } -impl PyStdioRaw { - fn new() -> Self { - let pystream: Py = Python::with_gil(|py| { - let module = PyModule::import_bound(py, "sys").unwrap(); - module.getattr(T::STREAM).unwrap().into() - }); +/// Construct a new handle to Python's `sys.__stderr__` stream. +pub fn __stderr__() -> PyWriter { + get_stdio_writer("__stderr__") +} - Self { - pystream, - _phantom: PhantomData, - } - } -} -impl Write for PyStdioRaw { +impl Write for PyWriter { fn write(&mut self, buf: &[u8]) -> std::io::Result { - Python::with_gil(|_py| unsafe { - (T::PRINTFCN)( - b"%.*s\0".as_ptr().cast(), - buf.len() as c_int, - buf.as_ptr() as *const c_char, - ); - }); + Python::with_gil(|py| -> std::io::Result { + let str = PyString::new_bound(py,&String::from_utf8_lossy(buf)); + self.0 + .call_method1(py,intern!(py, "write"), (str,)) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; Ok(buf.len()) + }) } fn flush(&mut self) -> std::io::Result<()> { Python::with_gil(|py| -> std::io::Result<()> { - self.pystream + self.0 .call_method0(py, intern!(py, "flush")) .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; Ok(()) }) } -} - -struct PyStdio { - inner: LineWriter>, -} - -impl PyStdio { - fn new() -> Self { - Self { - inner: LineWriter::new(PyStdioRaw::new()), - } - } -} - -impl Write for PyStdio { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.inner.write(buf) - } - fn flush(&mut self) -> std::io::Result<()> { - self.inner.flush() - } -} - -/// A handle to Python's `sys.stdout` stream. -pub struct PyStdout(PyStdio); -/// A handle to Python's `sys.stderr` stream. -pub struct PyStderr(PyStdio); - -/// Construct a new handle to Python's `sys.stdout` stream. -pub fn stdout() -> PyStdout { - PyStdout(PyStdio::new()) -} -/// Construct a new handle to Python's `sys.stderr` stream. -pub fn stderr() -> PyStderr { - PyStderr(PyStdio::new()) -} - -impl Write for PyStdout { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.0.write(buf) - } - fn flush(&mut self) -> std::io::Result<()> { - self.0.flush() - } -} -impl Write for PyStderr { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.0.write(buf) - } - fn flush(&mut self) -> std::io::Result<()> { - self.0.flush() - } -} +} \ No newline at end of file From e90f22084d5c121181b6132b7780d894bd9ccecf Mon Sep 17 00:00:00 2001 From: goulart-paul Date: Mon, 4 Mar 2024 20:54:47 +0000 Subject: [PATCH 12/14] add Bound variant --- src/stdio.rs | 65 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/src/stdio.rs b/src/stdio.rs index 6e4e8fc53e0..810b2ac127d 100644 --- a/src/stdio.rs +++ b/src/stdio.rs @@ -19,53 +19,72 @@ use crate::intern; use crate::prelude::*; use std::io::{LineWriter, Write}; +/// Implements `std::io::Write` for a `PyAny` object. The underlying +/// Python object must provide both `write` and `flush` methods. pub struct PyWriter(Py); -fn get_stdio_writer(stream: &str) -> PyWriter { +impl PyWriter { + /// Construct a new `PyWriter` from a `PyAny` object. + pub fn buffered(self) -> LineWriter { + LineWriter::new(self) + } +} + +/// A GIL-attached equivalent to PyWriter. +pub struct PyWriterBound<'a, 'py>(&'a Bound<'py, PyAny>); + +fn get_stdio_pywriter(stream: &str) -> PyWriter { Python::with_gil(|py| { let module = PyModule::import_bound(py, "sys").unwrap(); - module.getattr(stream).unwrap(); - PyWriter(module.into()) + let stream = module.getattr(stream).unwrap(); + PyWriter(stream.into()) }) } -/// Construct a new handle to Python's `sys.stdout` stream. +/// Construct a new `PyWriter` for Python's `sys.stdout` stream. pub fn stdout() -> PyWriter { - get_stdio_writer("stdout") + get_stdio_pywriter("stdout") } -/// Construct a new handle to Python's `sys.stderr` stream. +/// Construct a new `PyWriter` for Python's `sys.stderr` stream. pub fn stderr() -> PyWriter { - get_stdio_writer("stderr") + get_stdio_pywriter("stderr") } -/// Construct a new handle to Python's `sys.__stdout__` stream. +/// Construct a new `PyWriter` for Python's `sys.__stdout__` stream. pub fn __stdout__() -> PyWriter { - get_stdio_writer("__stdout__") + get_stdio_pywriter("__stdout__") } -/// Construct a new handle to Python's `sys.__stderr__` stream. +/// Construct a new `PyWriter` for Python's `sys.__stderr__` stream. pub fn __stderr__() -> PyWriter { - get_stdio_writer("__stderr__") + get_stdio_pywriter("__stderr__") } - impl Write for PyWriter { fn write(&mut self, buf: &[u8]) -> std::io::Result { - Python::with_gil(|py| -> std::io::Result { - let str = PyString::new_bound(py,&String::from_utf8_lossy(buf)); - self.0 - .call_method1(py,intern!(py, "write"), (str,)) + Python::with_gil(|py| { + PyWriterBound(self.0.bind(py)).write(buf) + }) + } + fn flush(&mut self) -> std::io::Result<()> { + Python::with_gil(|py| { + PyWriterBound(self.0.bind(py)).flush() + }) + } +} + +impl Write for PyWriterBound<'_, '_> { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + let str = PyString::new_bound(self.0.py(),&String::from_utf8_lossy(buf)); + self.0.call_method1(intern!(self.0.py(),"write"), (str,)) .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; Ok(buf.len()) - }) } fn flush(&mut self) -> std::io::Result<()> { - Python::with_gil(|py| -> std::io::Result<()> { - self.0 - .call_method0(py, intern!(py, "flush")) + self.0.call_method0(intern!(self.0.py(),"flush")) .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; - Ok(()) - }) + Ok(()) } -} \ No newline at end of file +} + From f328a202f407f946a6aeffd441dc6c53c3ddc330 Mon Sep 17 00:00:00 2001 From: goulart-paul Date: Tue, 5 Mar 2024 14:27:48 +0000 Subject: [PATCH 13/14] added unit test --- src/stdio.rs | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/stdio.rs b/src/stdio.rs index 810b2ac127d..b994a158783 100644 --- a/src/stdio.rs +++ b/src/stdio.rs @@ -14,12 +14,12 @@ //! writeln!(stdout, "Hello, world!").unwrap(); //! ``` -use crate::types::PyString; use crate::intern; use crate::prelude::*; +use crate::types::PyString; use std::io::{LineWriter, Write}; -/// Implements `std::io::Write` for a `PyAny` object. The underlying +/// Implements `std::io::Write` for a `PyAny` object. The underlying /// Python object must provide both `write` and `flush` methods. pub struct PyWriter(Py); @@ -49,7 +49,7 @@ pub fn stdout() -> PyWriter { /// Construct a new `PyWriter` for Python's `sys.stderr` stream. pub fn stderr() -> PyWriter { get_stdio_pywriter("stderr") -} +} /// Construct a new `PyWriter` for Python's `sys.__stdout__` stream. pub fn __stdout__() -> PyWriter { @@ -59,32 +59,29 @@ pub fn __stdout__() -> PyWriter { /// Construct a new `PyWriter` for Python's `sys.__stderr__` stream. pub fn __stderr__() -> PyWriter { get_stdio_pywriter("__stderr__") -} +} impl Write for PyWriter { fn write(&mut self, buf: &[u8]) -> std::io::Result { - Python::with_gil(|py| { - PyWriterBound(self.0.bind(py)).write(buf) - }) + Python::with_gil(|py| PyWriterBound(self.0.bind(py)).write(buf)) } fn flush(&mut self) -> std::io::Result<()> { - Python::with_gil(|py| { - PyWriterBound(self.0.bind(py)).flush() - }) + Python::with_gil(|py| PyWriterBound(self.0.bind(py)).flush()) } } -impl Write for PyWriterBound<'_, '_> { +impl Write for PyWriterBound<'_, '_> { fn write(&mut self, buf: &[u8]) -> std::io::Result { - let str = PyString::new_bound(self.0.py(),&String::from_utf8_lossy(buf)); - self.0.call_method1(intern!(self.0.py(),"write"), (str,)) + let str = PyString::new_bound(self.0.py(), &String::from_utf8_lossy(buf)); + self.0 + .call_method1(intern!(self.0.py(), "write"), (str,)) .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; Ok(buf.len()) } fn flush(&mut self) -> std::io::Result<()> { - self.0.call_method0(intern!(self.0.py(),"flush")) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + self.0 + .call_method0(intern!(self.0.py(), "flush")) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; Ok(()) } } - From e2b8e5334b29aaec3298cb974e618992723f78e5 Mon Sep 17 00:00:00 2001 From: goulart-paul Date: Tue, 5 Mar 2024 14:39:09 +0000 Subject: [PATCH 14/14] actually added unit test this time --- tests/test_stdio.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tests/test_stdio.rs diff --git a/tests/test_stdio.rs b/tests/test_stdio.rs new file mode 100644 index 00000000000..aba353bdc18 --- /dev/null +++ b/tests/test_stdio.rs @@ -0,0 +1,47 @@ +#![cfg(feature = "macros")] + +use pyo3::prelude::*; + +#[macro_use] +#[path = "../src/tests/common.rs"] +mod common; + +#[test] +fn test_stdio() { + use pyo3::stdio::*; + use pyo3::types::IntoPyDict; + use std::io::Write; + + let stream_fcns = [stdout, stderr, __stdout__, __stderr__]; + let stream_names = ["stdout", "stderr", "__stdout__", "__stderr__"]; + + for (stream_fcn, stream_name) in stream_fcns.iter().zip(stream_names.iter()) { + Python::with_gil(|py| { + py.run_bound("import sys, io", None, None).unwrap(); + + // redirect stdout or stderr output to a StringIO object + let target = py.eval_bound("io.StringIO()", None, None).unwrap(); + let locals = [("target", &target)].into_py_dict_bound(py); + py.run_bound( + &format!("sys.{} = target", stream_name), + None, + Some(&locals), + ) + .unwrap(); + + let mut stream = stream_fcn(); + assert!(writeln!(stream, "writing to {}", stream_name).is_ok()); + + Python::run_bound( + py, + &format!( + "assert target.getvalue() == 'writing to {}\\n'", + stream_name + ), + Some(&locals), + None, + ) + .unwrap(); + }); + } +}