1#[cfg(any(doc, all(Py_3_14, not(Py_LIMITED_API))))]
2use crate::{types::PyString, Python};
3#[cfg(all(Py_3_14, not(Py_LIMITED_API)))]
4use {
5 crate::ffi::{
6 PyUnicodeWriter_Create, PyUnicodeWriter_Discard, PyUnicodeWriter_Finish,
7 PyUnicodeWriter_WriteChar, PyUnicodeWriter_WriteUTF8,
8 },
9 crate::ffi_ptr_ext::FfiPtrExt,
10 crate::impl_::callback::WrappingCastTo,
11 crate::py_result_ext::PyResultExt,
12 crate::IntoPyObject,
13 crate::{ffi, Bound, PyErr, PyResult},
14 std::fmt,
15 std::mem::ManuallyDrop,
16 std::ptr::NonNull,
17};
18
19#[macro_export]
39macro_rules! py_format {
40 ($py: expr, $($arg:tt)*) => {{
41 if let Some(static_string) = format_args!($($arg)*).as_str() {
42 static INTERNED: $crate::sync::PyOnceLock<$crate::Py<$crate::types::PyString>> = $crate::sync::PyOnceLock::new();
43 Ok(
44 INTERNED
45 .get_or_init($py, || $crate::types::PyString::intern($py, static_string).unbind())
46 .bind($py)
47 .to_owned()
48 )
49 } else {
50 $crate::types::PyString::from_fmt($py, format_args!($($arg)*))
51 }
52 }}
53}
54
55#[cfg(all(Py_3_14, not(Py_LIMITED_API)))]
56pub(crate) struct PyUnicodeWriter<'py> {
58 python: Python<'py>,
59 writer: NonNull<ffi::PyUnicodeWriter>,
60 last_error: Option<PyErr>,
61}
62
63#[cfg(all(Py_3_14, not(Py_LIMITED_API)))]
64impl<'py> PyUnicodeWriter<'py> {
65 pub fn new(py: Python<'py>) -> PyResult<Self> {
67 Self::with_capacity(py, 0)
68 }
69
70 #[inline]
72 pub fn with_capacity(py: Python<'py>, capacity: usize) -> PyResult<Self> {
73 match NonNull::new(unsafe { PyUnicodeWriter_Create(capacity.wrapping_cast()) }) {
74 Some(ptr) => Ok(PyUnicodeWriter {
75 python: py,
76 writer: ptr,
77 last_error: None,
78 }),
79 None => Err(PyErr::fetch(py)),
80 }
81 }
82
83 #[inline]
85 pub fn into_py_string(mut self) -> PyResult<Bound<'py, PyString>> {
86 let py = self.python;
87 if let Some(error) = self.take_error() {
88 Err(error)
89 } else {
90 unsafe {
91 PyUnicodeWriter_Finish(ManuallyDrop::new(self).as_ptr())
92 .assume_owned_or_err(py)
93 .cast_into_unchecked()
94 }
95 }
96 }
97
98 #[inline]
100 pub fn take_error(&mut self) -> Option<PyErr> {
101 self.last_error.take()
102 }
103
104 #[inline]
105 fn as_ptr(&self) -> *mut ffi::PyUnicodeWriter {
106 self.writer.as_ptr()
107 }
108
109 #[inline]
110 fn set_error(&mut self) {
111 self.last_error = Some(PyErr::fetch(self.python));
112 }
113}
114
115#[cfg(all(Py_3_14, not(Py_LIMITED_API)))]
116impl fmt::Write for PyUnicodeWriter<'_> {
117 #[inline]
118 fn write_str(&mut self, s: &str) -> fmt::Result {
119 let result = unsafe {
120 PyUnicodeWriter_WriteUTF8(self.as_ptr(), s.as_ptr().cast(), s.len() as isize)
121 };
122 if result < 0 {
123 self.set_error();
124 Err(fmt::Error)
125 } else {
126 Ok(())
127 }
128 }
129
130 #[inline]
131 fn write_char(&mut self, c: char) -> fmt::Result {
132 let result = unsafe { PyUnicodeWriter_WriteChar(self.as_ptr(), c.into()) };
133 if result < 0 {
134 self.set_error();
135 Err(fmt::Error)
136 } else {
137 Ok(())
138 }
139 }
140}
141
142#[cfg(all(Py_3_14, not(Py_LIMITED_API)))]
143impl Drop for PyUnicodeWriter<'_> {
144 #[inline]
145 fn drop(&mut self) {
146 unsafe {
147 PyUnicodeWriter_Discard(self.as_ptr());
148 }
149 }
150}
151
152#[cfg(all(Py_3_14, not(Py_LIMITED_API)))]
153impl<'py> IntoPyObject<'py> for PyUnicodeWriter<'py> {
154 type Target = PyString;
155 type Output = Bound<'py, Self::Target>;
156 type Error = PyErr;
157
158 #[inline]
159 fn into_pyobject(self, _py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
160 self.into_py_string()
161 }
162}
163
164#[cfg(all(Py_3_14, not(Py_LIMITED_API)))]
165impl<'py> TryInto<Bound<'py, PyString>> for PyUnicodeWriter<'py> {
166 type Error = PyErr;
167
168 #[inline]
169 fn try_into(self) -> PyResult<Bound<'py, PyString>> {
170 self.into_py_string()
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 #[cfg(all(Py_3_14, not(Py_LIMITED_API)))]
177 use super::*;
178 use crate::types::PyStringMethods;
179 use crate::{IntoPyObject, Python};
180
181 #[test]
182 #[allow(clippy::write_literal)]
183 #[cfg(all(Py_3_14, not(Py_LIMITED_API)))]
184 fn unicode_writer_test() {
185 use std::fmt::Write;
186 Python::attach(|py| {
187 let mut writer = PyUnicodeWriter::new(py).unwrap();
188 write!(writer, "Hello {}!", "world").unwrap();
189 writer.write_char('😎').unwrap();
190 let result = writer.into_py_string().unwrap();
191 assert_eq!(result.to_string(), "Hello world!😎");
192 });
193 }
194
195 #[test]
196 #[allow(clippy::write_literal)]
197 #[cfg(all(Py_3_14, not(Py_LIMITED_API)))]
198 fn unicode_writer_with_capacity() {
199 use std::fmt::Write;
200 Python::attach(|py| {
201 let mut writer = PyUnicodeWriter::with_capacity(py, 10).unwrap();
202 write!(writer, "Hello {}!", "world").unwrap();
203 writer.write_char('😎').unwrap();
204 let result = writer.into_py_string().unwrap();
205 assert_eq!(result.to_string(), "Hello world!😎");
206 });
207 }
208
209 #[test]
210 fn test_pystring_from_fmt() {
211 Python::attach(|py| {
212 py_format!(py, "Hello {}!", "world").unwrap();
213 });
214 }
215
216 #[test]
217 fn test_complex_format() {
218 Python::attach(|py| {
219 let complex_value = (42, "foo", [0; 0]).into_pyobject(py).unwrap();
220 let py_string = py_format!(py, "This is some complex value: {complex_value}").unwrap();
221 let actual = py_string.to_cow().unwrap();
222 let expected = "This is some complex value: (42, 'foo', [])";
223 assert_eq!(actual, expected);
224 });
225 }
226}