Vendor deps.

This commit is contained in:
jimmy committed 2026-09-16 20:10:35 -04:00
1 parent 294527085e
commit 91e18fde3a
1714 files changed
+556667 -7

No files matched your search

+1083
View File
File diff suppressed because it is too large. Load diff
+281
View File
@@ -0,0 +1,281 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
#[cfg(feature = "experimental-inspect")]
use crate::PyTypeInfo;
#[cfg(not(Py_LIMITED_API))]
use crate::{
err::error_on_minusone,
ffi::{
self,
compat::{
PyBytesWriter_Create, PyBytesWriter_Discard, PyBytesWriter_Finish,
PyBytesWriter_GetData, PyBytesWriter_GetSize, PyBytesWriter_Resize,
},
},
ffi_ptr_ext::FfiPtrExt,
py_result_ext::PyResultExt,
};
use crate::{types::PyBytes, Bound, IntoPyObject, PyErr, PyResult, Python};
#[cfg(not(Py_LIMITED_API))]
use core::{
mem::ManuallyDrop,
ptr::{self, NonNull},
};
use std::io::IoSlice;
pub struct PyBytesWriter<'py> {
python: Python<'py>,
#[cfg(not(Py_LIMITED_API))]
writer: NonNull<ffi::compat::PyBytesWriter>,
#[cfg(Py_LIMITED_API)]
buffer: Vec<u8>,
}
impl<'py> PyBytesWriter<'py> {
/// Create a new `PyBytesWriter` with a default initial capacity.
#[inline]
pub fn new(py: Python<'py>) -> PyResult<Self> {
Self::with_capacity(py, 0)
}
/// Create a new `PyBytesWriter` with the specified initial capacity.
#[inline]
#[cfg_attr(Py_LIMITED_API, allow(clippy::unnecessary_wraps))]
pub fn with_capacity(py: Python<'py>, capacity: usize) -> PyResult<Self> {
cfg_select! {
not(Py_LIMITED_API) => NonNull::new(unsafe { PyBytesWriter_Create(capacity as _) }).map_or_else(
|| Err(PyErr::fetch(py)),
|writer| {
let mut writer = PyBytesWriter { python: py, writer };
if capacity > 0 {
// SAFETY: By setting the length to 0, we ensure no bytes are considered uninitialized.
unsafe { writer.set_len(0)? };
}
Ok(writer)
},
),
Py_LIMITED_API => Ok(PyBytesWriter {
python: py,
buffer: Vec::with_capacity(capacity),
})
}
}
/// Get the current length of the internal buffer.
#[inline]
pub fn len(&self) -> usize {
cfg_select! {
not(Py_LIMITED_API) => unsafe {
PyBytesWriter_GetSize(self.writer.as_ptr()) as _
},
Py_LIMITED_API => self.buffer.len()
}
}
#[inline]
#[cfg(not(Py_LIMITED_API))]
fn as_mut_ptr(&mut self) -> *mut u8 {
unsafe { PyBytesWriter_GetData(self.writer.as_ptr()) as _ }
}
/// Set the length of the internal buffer to `new_len`. The new bytes are uninitialized.
///
/// # Safety
/// The caller must ensure the new bytes are initialized. This will also make all pointers
/// returned by `as_mut_ptr` invalid, so the caller must not hold any references to the buffer
/// across this call.
#[inline]
#[cfg(not(Py_LIMITED_API))]
unsafe fn set_len(&mut self, new_len: usize) -> PyResult<()> {
unsafe {
error_on_minusone(
self.python,
PyBytesWriter_Resize(self.writer.as_ptr(), new_len as _),
)
}
}
}
impl<'py> TryFrom<PyBytesWriter<'py>> for Bound<'py, PyBytes> {
type Error = PyErr;
#[inline]
fn try_from(value: PyBytesWriter<'py>) -> Result<Self, Self::Error> {
let py = value.python;
cfg_select! {
// SAFETY:
// - we no longer use `value` after this call
// - `PyBytesWriter_Finish` will return a new reference to a bytes object on success
not(Py_LIMITED_API) => unsafe {
PyBytesWriter_Finish(ManuallyDrop::new(value).writer.as_ptr())
.assume_owned_or_err(py)
.cast_into_unchecked()
},
Py_LIMITED_API => Ok(PyBytes::new(py, &value.buffer))
}
}
}
impl<'py> IntoPyObject<'py> for PyBytesWriter<'py> {
type Target = PyBytes;
type Output = Bound<'py, PyBytes>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = PyBytes::TYPE_HINT;
#[inline]
fn into_pyobject(self, _py: Python<'py>) -> Result<Self::Output, Self::Error> {
self.try_into()
}
}
#[cfg(not(Py_LIMITED_API))]
impl<'py> Drop for PyBytesWriter<'py> {
#[inline]
fn drop(&mut self) {
unsafe { PyBytesWriter_Discard(self.writer.as_ptr()) }
}
}
#[cfg(not(Py_LIMITED_API))]
impl std::io::Write for PyBytesWriter<'_> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.write_all(buf)?;
Ok(buf.len())
}
fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> std::io::Result<usize> {
let len = bufs.iter().map(|b| b.len()).sum();
let pos = self.len();
// SAFETY: We write the new uninitialized bytes below.
unsafe { self.set_len(self.len() + len)? }
// SAFETY: We ensured enough capacity above and the ptr will be valid because we will not be
// resizing the buffer until we have written all the data.
let mut ptr = unsafe { self.as_mut_ptr().add(pos) };
for buf in bufs {
// SAFETY: We have ensured enough capacity above.
unsafe { ptr::copy_nonoverlapping(buf.as_ptr(), ptr, buf.len()) };
// SAFETY: We just wrote buf.len() bytes
ptr = unsafe { ptr.add(buf.len()) };
}
Ok(len)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
let len = buf.len();
let pos = self.len();
// SAFETY: We write the new uninitialized bytes below.
unsafe { self.set_len(pos + len)? }
// SAFETY: We have ensured enough capacity above.
unsafe { ptr::copy_nonoverlapping(buf.as_ptr(), self.as_mut_ptr().add(pos), len) };
Ok(())
}
}
#[cfg(Py_LIMITED_API)]
impl std::io::Write for PyBytesWriter<'_> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.buffer.write(buf)
}
fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> std::io::Result<usize> {
self.buffer.write_vectored(bufs)
}
fn flush(&mut self) -> std::io::Result<()> {
self.buffer.flush()
}
fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
self.buffer.write_all(buf)
}
fn write_fmt(&mut self, args: core::fmt::Arguments<'_>) -> std::io::Result<()> {
self.buffer.write_fmt(args)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::PyBytesMethods;
use std::io::Write;
#[test]
fn test_io_write() {
Python::attach(|py| {
let buf = b"hallo world";
let mut writer = PyBytesWriter::new(py).unwrap();
assert_eq!(writer.write(buf).unwrap(), 11);
let bytes: Bound<'_, PyBytes> = writer.try_into().unwrap();
assert_eq!(bytes.as_bytes(), buf);
})
}
#[test]
fn test_pre_allocated() {
Python::attach(|py| {
let buf = b"hallo world";
let mut writer = PyBytesWriter::with_capacity(py, buf.len()).unwrap();
assert_eq!(writer.len(), 0, "Writer position should be zero");
assert_eq!(writer.write(buf).unwrap(), 11);
let bytes: Bound<'_, PyBytes> = writer.try_into().unwrap();
assert_eq!(bytes.as_bytes(), buf);
})
}
#[test]
fn test_io_write_vectored() {
Python::attach(|py| {
let bufs = [IoSlice::new(b"hallo "), IoSlice::new(b"world")];
let mut writer = PyBytesWriter::new(py).unwrap();
assert_eq!(writer.write_vectored(&bufs).unwrap(), 11);
let bytes: Bound<'_, PyBytes> = writer.try_into().unwrap();
assert_eq!(bytes.as_bytes(), b"hallo world");
})
}
#[test]
fn test_io_write_vectored_large() {
Python::attach(|py| {
let large_data = vec![b'\n'; 1024]; // 1 KB
let bufs = [
IoSlice::new(b"hallo"),
IoSlice::new(&large_data),
IoSlice::new(b"world"),
];
let mut writer = PyBytesWriter::new(py).unwrap();
assert_eq!(writer.write_vectored(&bufs).unwrap(), 1034);
let bytes: Bound<'_, PyBytes> = writer.try_into().unwrap();
assert!(bytes.as_bytes().starts_with(b"hallo\n"));
assert!(bytes.as_bytes().ends_with(b"world"));
assert_eq!(bytes.as_bytes().len(), 1034);
})
}
#[test]
fn test_large_data() {
Python::attach(|py| {
let mut writer = PyBytesWriter::new(py).unwrap();
let large_data = vec![0; 1024]; // 1 KB
writer.write_all(&large_data).unwrap();
let bytes: Bound<'_, PyBytes> = writer.try_into().unwrap();
assert_eq!(bytes.as_bytes(), large_data.as_slice());
})
}
}
+315
View File
@@ -0,0 +1,315 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
//! Defines how Python calls are dispatched, see [`PyCallArgs`].for more information.
use crate::ffi_ptr_ext::FfiPtrExt as _;
use crate::types::{PyAnyMethods as _, PyDict, PyString, PyTuple};
use crate::{ffi, Borrowed, Bound, IntoPyObjectExt as _, Py, PyAny, PyResult};
pub(crate) mod private {
use super::*;
pub trait Sealed {}
impl Sealed for () {}
impl Sealed for Bound<'_, PyTuple> {}
impl Sealed for &'_ Bound<'_, PyTuple> {}
impl Sealed for Py<PyTuple> {}
impl Sealed for &'_ Py<PyTuple> {}
impl Sealed for Borrowed<'_, '_, PyTuple> {}
pub struct Token;
}
/// This trait marks types that can be used as arguments to Python function
/// calls.
///
/// This trait is currently implemented for Rust tuple (up to a size of 12),
/// [`Bound<'py, PyTuple>`] and [`Py<PyTuple>`]. Custom types that are
/// convertible to `PyTuple` via `IntoPyObject` need to do so before passing it
/// to `call`.
///
/// This trait is not intended to used by downstream crates directly. As such it
/// has no publicly available methods and cannot be implemented outside of
/// `pyo3`. The corresponding public API is available through [`call`]
/// ([`call0`], [`call1`] and friends) on [`PyAnyMethods`].
///
/// # What is `PyCallArgs` used for?
/// `PyCallArgs` is used internally in `pyo3` to dispatch the Python calls in
/// the most optimal way for the current build configuration. Certain types,
/// such as Rust tuples, do allow the usage of a faster calling convention of
/// the Python interpreter (if available). More types that may take advantage
/// from this may be added in the future.
///
/// [`call0`]: crate::types::PyAnyMethods::call0
/// [`call1`]: crate::types::PyAnyMethods::call1
/// [`call`]: crate::types::PyAnyMethods::call
/// [`PyAnyMethods`]: crate::types::PyAnyMethods
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot used as a Python `call` argument",
note = "`PyCallArgs` is implemented for Rust tuples, `Bound<'py, PyTuple>` and `Py<PyTuple>`",
note = "if your type is convertible to `PyTuple` via `IntoPyObject`, call `<arg>.into_pyobject(py)` manually",
note = "if you meant to pass the type as a single argument, wrap it in a 1-tuple, `(<arg>,)`"
)]
pub trait PyCallArgs<'py>: Sized + private::Sealed {
#[doc(hidden)]
fn call(
self,
function: Borrowed<'_, 'py, PyAny>,
kwargs: Borrowed<'_, 'py, PyDict>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>>;
#[doc(hidden)]
fn call_positional(
self,
function: Borrowed<'_, 'py, PyAny>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>>;
#[doc(hidden)]
fn call_method_positional(
self,
object: Borrowed<'_, 'py, PyAny>,
method_name: Borrowed<'_, 'py, PyString>,
_: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
object
.getattr(method_name)
.and_then(|method| method.call1(self))
}
}
impl<'py> PyCallArgs<'py> for () {
fn call(
self,
function: Borrowed<'_, 'py, PyAny>,
kwargs: Borrowed<'_, 'py, PyDict>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
let args = self.into_pyobject_or_pyerr(function.py())?;
args.call(function, kwargs, token)
}
fn call_positional(
self,
function: Borrowed<'_, 'py, PyAny>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
let args = self.into_pyobject_or_pyerr(function.py())?;
args.call_positional(function, token)
}
}
impl<'py> PyCallArgs<'py> for Bound<'py, PyTuple> {
#[inline]
fn call(
self,
function: Borrowed<'_, 'py, PyAny>,
kwargs: Borrowed<'_, 'py, PyDict>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
self.as_borrowed().call(function, kwargs, token)
}
#[inline]
fn call_positional(
self,
function: Borrowed<'_, 'py, PyAny>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
self.as_borrowed().call_positional(function, token)
}
}
impl<'py> PyCallArgs<'py> for &'_ Bound<'py, PyTuple> {
#[inline]
fn call(
self,
function: Borrowed<'_, 'py, PyAny>,
kwargs: Borrowed<'_, 'py, PyDict>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
self.as_borrowed().call(function, kwargs, token)
}
#[inline]
fn call_positional(
self,
function: Borrowed<'_, 'py, PyAny>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
self.as_borrowed().call_positional(function, token)
}
}
impl<'py> PyCallArgs<'py> for Py<PyTuple> {
#[inline]
fn call(
self,
function: Borrowed<'_, 'py, PyAny>,
kwargs: Borrowed<'_, 'py, PyDict>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
self.bind_borrowed(function.py())
.call(function, kwargs, token)
}
#[inline]
fn call_positional(
self,
function: Borrowed<'_, 'py, PyAny>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
self.bind_borrowed(function.py())
.call_positional(function, token)
}
}
impl<'py> PyCallArgs<'py> for &'_ Py<PyTuple> {
#[inline]
fn call(
self,
function: Borrowed<'_, 'py, PyAny>,
kwargs: Borrowed<'_, 'py, PyDict>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
self.bind_borrowed(function.py())
.call(function, kwargs, token)
}
#[inline]
fn call_positional(
self,
function: Borrowed<'_, 'py, PyAny>,
token: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
self.bind_borrowed(function.py())
.call_positional(function, token)
}
}
impl<'py> PyCallArgs<'py> for Borrowed<'_, 'py, PyTuple> {
#[inline]
fn call(
self,
function: Borrowed<'_, 'py, PyAny>,
kwargs: Borrowed<'_, 'py, PyDict>,
_: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
unsafe {
ffi::PyObject_Call(function.as_ptr(), self.as_ptr(), kwargs.as_ptr())
.assume_owned_or_err(function.py())
}
}
#[inline]
fn call_positional(
self,
function: Borrowed<'_, 'py, PyAny>,
_: private::Token,
) -> PyResult<Bound<'py, PyAny>> {
unsafe {
ffi::PyObject_Call(function.as_ptr(), self.as_ptr(), core::ptr::null_mut())
.assume_owned_or_err(function.py())
}
}
}
#[cfg(test)]
#[cfg(feature = "macros")]
mod tests {
use crate::{
pyfunction,
types::{PyDict, PyTuple},
Py,
};
#[pyfunction(signature = (*args, **kwargs), crate = "crate")]
fn args_kwargs(
args: Py<PyTuple>,
kwargs: Option<Py<PyDict>>,
) -> (Py<PyTuple>, Option<Py<PyDict>>) {
(args, kwargs)
}
#[test]
fn test_call() {
use crate::{
types::{IntoPyDict, PyAnyMethods, PyDict, PyTuple},
wrap_pyfunction, Py, Python,
};
Python::attach(|py| {
let f = wrap_pyfunction!(args_kwargs, py).unwrap();
let args = PyTuple::new(py, [1, 2, 3]).unwrap();
let kwargs = &[("foo", 1), ("bar", 2)].into_py_dict(py).unwrap();
macro_rules! check_call {
($args:expr, $kwargs:expr) => {
let (a, k): (Py<PyTuple>, Py<PyDict>) = f
.call(args.clone(), Some(kwargs))
.unwrap()
.extract()
.unwrap();
assert!(a.is(&args));
assert!(k.is(kwargs));
};
}
// Bound<'py, PyTuple>
check_call!(args.clone(), kwargs);
// &Bound<'py, PyTuple>
check_call!(&args, kwargs);
// Py<PyTuple>
check_call!(args.clone().unbind(), kwargs);
// &Py<PyTuple>
check_call!(&args.as_unbound(), kwargs);
// Borrowed<'_, '_, PyTuple>
check_call!(args.as_borrowed(), kwargs);
})
}
#[test]
fn test_call_positional() {
use crate::{
types::{PyAnyMethods, PyNone, PyTuple},
wrap_pyfunction, Py, Python,
};
Python::attach(|py| {
let f = wrap_pyfunction!(args_kwargs, py).unwrap();
let args = PyTuple::new(py, [1, 2, 3]).unwrap();
macro_rules! check_call {
($args:expr, $kwargs:expr) => {
let (a, k): (Py<PyTuple>, Py<PyNone>) =
f.call1(args.clone()).unwrap().extract().unwrap();
assert!(a.is(&args));
assert!(k.is_none(py));
};
}
// Bound<'py, PyTuple>
check_call!(args.clone(), kwargs);
// &Bound<'py, PyTuple>
check_call!(&args, kwargs);
// Py<PyTuple>
check_call!(args.clone().unbind(), kwargs);
// &Py<PyTuple>
check_call!(args.as_unbound(), kwargs);
// Borrowed<'_, '_, PyTuple>
check_call!(args.as_borrowed(), kwargs);
})
}
}
+646
View File
@@ -0,0 +1,646 @@
//! Defines conversions between Rust and Python types.
use crate::err::PyResult;
use crate::impl_::pyclass::ExtractPyClassWithClone;
#[cfg(feature = "experimental-inspect")]
use crate::inspect::{type_hint_identifier, type_hint_subscript, PyStaticExpr};
use crate::pyclass::boolean_struct::False;
use crate::pyclass::{PyClassGuardError, PyClassGuardMutError};
use crate::types::PyList;
use crate::types::PyTuple;
use crate::{
Borrowed, Bound, BoundObject, Py, PyAny, PyClass, PyClassGuard, PyErr, PyRef, PyRefMut,
PyTypeCheck, Python,
};
use core::convert::Infallible;
use core::marker::PhantomData;
/// Defines a conversion from a Rust type to a Python object, which may fail.
///
/// This trait has `#[derive(IntoPyObject)]` to automatically implement it for simple types and
/// `#[derive(IntoPyObjectRef)]` to implement the same for references.
///
/// It functions similarly to std's [`TryInto`] trait, but requires a [`Python<'py>`] token
/// as an argument.
///
/// The [`into_pyobject`][IntoPyObject::into_pyobject] method is designed for maximum flexibility and efficiency; it
/// - allows for a concrete Python type to be returned (the [`Target`][IntoPyObject::Target] associated type)
/// - allows for the smart pointer containing the Python object to be either `Bound<'py, Self::Target>` or `Borrowed<'a, 'py, Self::Target>`
/// to avoid unnecessary reference counting overhead
/// - allows for a custom error type to be returned in the event of a conversion error to avoid
/// unnecessarily creating a Python exception
///
/// # See also
///
/// - The [`IntoPyObjectExt`] trait, which provides convenience methods for common usages of
/// `IntoPyObject` which erase type information and convert errors to `PyErr`.
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be converted to a Python object",
note = "`IntoPyObject` is automatically implemented by the `#[pyclass]` macro",
note = "if you do not wish to have a corresponding Python type, implement it manually",
note = "if you do not own `{Self}` you can perform a manual conversion to one of the types in `pyo3::types::*`"
)]
pub trait IntoPyObject<'py>: Sized {
/// The Python output type
type Target;
/// The smart pointer type to use.
///
/// This will usually be [`Bound<'py, Target>`], but in special cases [`Borrowed<'a, 'py, Target>`] can be
/// used to minimize reference counting overhead.
type Output: BoundObject<'py, Self::Target>;
/// The type returned in the event of a conversion error.
type Error: Into<PyErr>;
/// Extracts the type hint information for this type when it appears as a return value.
///
/// For example, `Vec<u32>` would return `List[int]`.
/// The default implementation returns `Any`, which is correct for any type.
///
/// For most types, the return value for this method will be identical to that of [`FromPyObject::INPUT_TYPE`].
/// It may be different for some types, such as `Dict`, to allow duck-typing: functions return `Dict` but take `Mapping` as argument.
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("_typeshed", "Incomplete");
/// Performs the conversion.
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error>;
/// Converts sequence of Self into a Python object. Used to specialize `Vec<u8>`, `[u8; N]`
/// and `SmallVec<[u8; N]>` as a sequence of bytes into a `bytes` object.
#[doc(hidden)]
fn owned_sequence_into_pyobject<I>(
iter: I,
py: Python<'py>,
_: private::Token,
) -> Result<Bound<'py, PyAny>, PyErr>
where
I: IntoIterator<Item = Self> + AsRef<[Self]>,
I::IntoIter: ExactSizeIterator<Item = Self>,
{
Ok(PyList::new(py, iter)?.into_any())
}
/// Converts sequence of Self into a Python object. Used to specialize `&[u8]` and `Cow<[u8]>`
/// as a sequence of bytes into a `bytes` object.
#[doc(hidden)]
fn borrowed_sequence_into_pyobject<I>(
iter: I,
py: Python<'py>,
_: private::Token,
) -> Result<Bound<'py, PyAny>, PyErr>
where
Self: private::Reference,
I: IntoIterator<Item = Self> + AsRef<[<Self as private::Reference>::BaseType]>,
I::IntoIter: ExactSizeIterator<Item = Self>,
{
Ok(PyList::new(py, iter)?.into_any())
}
/// The output type of [`IntoPyObject::owned_sequence_into_pyobject`] and [`IntoPyObject::borrowed_sequence_into_pyobject`]
#[cfg(feature = "experimental-inspect")]
#[doc(hidden)]
const SEQUENCE_OUTPUT_TYPE: PyStaticExpr =
type_hint_subscript!(PyList::TYPE_HINT, Self::OUTPUT_TYPE);
}
pub(crate) mod private {
pub struct Token;
pub trait Reference {
type BaseType;
}
impl<T> Reference for &'_ T {
type BaseType = T;
}
}
impl<'py, T: PyTypeCheck> IntoPyObject<'py> for Bound<'py, T> {
type Target = T;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT;
fn into_pyobject(self, _py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self)
}
}
impl<'a, 'py, T: PyTypeCheck> IntoPyObject<'py> for &'a Bound<'py, T> {
type Target = T;
type Output = Borrowed<'a, 'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT;
fn into_pyobject(self, _py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.as_borrowed())
}
}
impl<'a, 'py, T: PyTypeCheck> IntoPyObject<'py> for Borrowed<'a, 'py, T> {
type Target = T;
type Output = Borrowed<'a, 'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT;
fn into_pyobject(self, _py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self)
}
}
impl<'a, 'py, T: PyTypeCheck> IntoPyObject<'py> for &Borrowed<'a, 'py, T> {
type Target = T;
type Output = Borrowed<'a, 'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT;
fn into_pyobject(self, _py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(*self)
}
}
impl<'py, T: PyTypeCheck> IntoPyObject<'py> for Py<T> {
type Target = T;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.into_bound(py))
}
}
impl<'a, 'py, T: PyTypeCheck> IntoPyObject<'py> for &'a Py<T> {
type Target = T;
type Output = Borrowed<'a, 'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.bind_borrowed(py))
}
}
impl<'a, 'py, T> IntoPyObject<'py> for &&'a T
where
&'a T: IntoPyObject<'py>,
{
type Target = <&'a T as IntoPyObject<'py>>::Target;
type Output = <&'a T as IntoPyObject<'py>>::Output;
type Error = <&'a T as IntoPyObject<'py>>::Error;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&'a T as IntoPyObject<'py>>::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
mod into_pyobject_ext {
pub trait Sealed {}
impl<'py, T> Sealed for T where T: super::IntoPyObject<'py> {}
}
/// Convenience methods for common usages of [`IntoPyObject`]. Every type that implements
/// [`IntoPyObject`] also implements this trait.
///
/// These methods:
/// - Drop type information from the output, returning a `PyAny` object.
/// - Always convert the `Error` type to `PyErr`, which may incur a performance penalty but it
/// more convenient in contexts where the `?` operator would produce a `PyErr` anyway.
pub trait IntoPyObjectExt<'py>: IntoPyObject<'py> + into_pyobject_ext::Sealed {
/// Converts `self` into an owned Python object, dropping type information.
#[inline]
fn into_bound_py_any(self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
match self.into_pyobject(py) {
Ok(obj) => Ok(obj.into_any().into_bound()),
Err(err) => Err(err.into()),
}
}
/// Converts `self` into an owned Python object, dropping type information and unbinding it
/// from the `'py` lifetime.
#[inline]
fn into_py_any(self, py: Python<'py>) -> PyResult<Py<PyAny>> {
match self.into_pyobject(py) {
Ok(obj) => Ok(obj.into_any().unbind()),
Err(err) => Err(err.into()),
}
}
/// Converts `self` into a Python object.
///
/// This is equivalent to calling [`into_pyobject`][IntoPyObject::into_pyobject] followed
/// with `.map_err(Into::into)` to convert the error type to [`PyErr`]. This is helpful
/// for generic code which wants to make use of the `?` operator.
#[inline]
fn into_pyobject_or_pyerr(self, py: Python<'py>) -> PyResult<Self::Output> {
match self.into_pyobject(py) {
Ok(obj) => Ok(obj),
Err(err) => Err(err.into()),
}
}
}
impl<'py, T> IntoPyObjectExt<'py> for T where T: IntoPyObject<'py> {}
/// Extract a type from a Python object.
///
///
/// Normal usage is through the `extract` methods on [`Bound`], [`Borrowed`] and [`Py`], which
/// forward to this trait.
///
/// # Examples
///
/// ```rust
/// use pyo3::prelude::*;
/// use pyo3::types::PyString;
///
/// # fn main() -> PyResult<()> {
/// Python::attach(|py| {
/// // Calling `.extract()` on a `Bound` smart pointer
/// let obj: Bound<'_, PyString> = PyString::new(py, "blah");
/// let s: String = obj.extract()?;
/// # assert_eq!(s, "blah");
///
/// // Calling `.extract(py)` on a `Py` smart pointer
/// let obj: Py<PyString> = obj.unbind();
/// let s: String = obj.extract(py)?;
/// # assert_eq!(s, "blah");
/// # Ok(())
/// })
/// # }
/// ```
///
/// Note: Depending on the Python version and implementation, some [`FromPyObject`] implementations
/// may produce a result that borrows into the Python type. This is described by the input lifetime
/// `'a` of `obj`.
///
/// Types that must not borrow from the input can use [`FromPyObjectOwned`] as a restriction. This
/// is most often the case for collection types. See its documentation for more details.
///
/// # How to implement [`FromPyObject`]?
/// ## `#[derive(FromPyObject)]`
/// The simplest way to implement [`FromPyObject`] for a custom type is to make use of our derive
/// macro.
/// ```rust,no_run
/// # #![allow(dead_code)]
/// use pyo3::prelude::*;
///
/// #[derive(FromPyObject)]
/// struct MyObject {
/// msg: String,
/// list: Vec<u32>
/// }
/// # fn main() {}
/// ```
/// By default this will try to extract each field from the Python object by attribute access, but
/// this can be customized. For more information about the derive macro, its configuration as well
/// as its working principle for other types, take a look at the [guide].
///
/// In case the derive macro is not sufficient or can not be used for some other reason,
/// [`FromPyObject`] can be implemented manually. In the following types without lifetime parameters
/// are handled first, because they are a little bit simpler. Types with lifetime parameters are
/// explained below.
///
/// ## Manual implementation for types without lifetime
/// Types that do not contain lifetime parameters are unable to borrow from the Python object, so
/// the lifetimes of [`FromPyObject`] can be elided:
/// ```rust,no_run
/// # #![allow(dead_code)]
/// use pyo3::prelude::*;
///
/// struct MyObject {
/// msg: String,
/// list: Vec<u32>
/// }
///
/// impl FromPyObject<'_, '_> for MyObject {
/// type Error = PyErr;
///
/// fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
/// Ok(MyObject {
/// msg: obj.getattr("msg")?.extract()?,
/// list: obj.getattr("list")?.extract()?,
/// })
/// }
/// }
///
/// # fn main() {}
/// ```
/// This is basically what the derive macro above expands to.
///
/// ## Manual implementation for types with lifetime parameters
/// For types that contain lifetimes, these lifetimes need to be bound to the corresponding
/// [`FromPyObject`] lifetime. This is roughly how the extraction of a typed [`Bound`] is
/// implemented within PyO3.
///
/// ```rust,no_run
/// # #![allow(dead_code)]
/// use pyo3::prelude::*;
/// use pyo3::types::PyString;
///
/// struct MyObject<'py>(Bound<'py, PyString>);
///
/// impl<'py> FromPyObject<'_, 'py> for MyObject<'py> {
/// type Error = PyErr;
///
/// fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {
/// Ok(MyObject(obj.cast()?.to_owned()))
/// }
/// }
///
/// # fn main() {}
/// ```
///
/// # Details
/// [`Cow<'a, str>`] is an example of an output type that may or may not borrow from the input
/// lifetime `'a`. Which variant will be produced depends on the runtime type of the Python object.
/// For a Python byte string, the existing string data can be borrowed for `'a` into a
/// [`Cow::Borrowed`]. For a Python Unicode string, the data may have to be reencoded to UTF-8, and
/// copied into a [`Cow::Owned`]. It does _not_ depend on the Python lifetime `'py`.
///
/// The output type may also depend on the Python lifetime `'py`. This allows the output type to
/// keep interacting with the Python interpreter. See also [`Bound<'py, T>`].
///
/// [`Cow<'a, str>`]: alloc::borrow::Cow
/// [`Cow::Borrowed`]: alloc::borrow::Cow::Borrowed
/// [`Cow::Owned`]: alloc::borrow::Cow::Owned
/// [guide]: https://pyo3.rs/latest/conversions/traits.html#deriving-frompyobject
pub trait FromPyObject<'a, 'py>: Sized {
/// The type returned in the event of a conversion error.
///
/// For most use cases defaulting to [PyErr] here is perfectly acceptable. Using a custom error
/// type can be used to avoid having to create a Python exception object in the case where that
/// exception never reaches Python. This may lead to slightly better performance under certain
/// conditions.
///
/// # Note
/// Unfortunately `Try` and thus `?` is based on [`From`], not [`Into`], so implementations may
/// need to use `.map_err(Into::into)` sometimes to convert a generic `Error` into a [`PyErr`].
type Error: Into<PyErr>;
/// Provides the type hint information for this type when it appears as an argument.
///
/// For example, `Vec<u32>` would be `collections.abc.Sequence[int]`.
/// The default value is `typing.Any`, which is correct for any type.
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = type_hint_identifier!("_typeshed", "Incomplete");
/// Extracts `Self` from the bound smart pointer `obj`.
///
/// Users are advised against calling this method directly: instead, use this via
/// [`Bound<'_, PyAny>::extract`](crate::types::any::PyAnyMethods::extract) or [`Py::extract`].
fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error>;
/// Specialization hook for extracting sequences for types like `Vec<u8>` and `[u8; N]`,
/// where the bytes can be directly copied from some python objects without going through
/// iteration.
#[doc(hidden)]
#[inline(always)]
fn sequence_extractor(
_obj: Borrowed<'_, 'py, PyAny>,
_: private::Token,
) -> Option<impl FromPyObjectSequence<Target = Self>> {
struct NeverASequence<T>(PhantomData<T>);
impl<T> FromPyObjectSequence for NeverASequence<T> {
type Target = T;
fn to_vec(&self) -> Vec<Self::Target> {
unreachable!()
}
fn to_array<const N: usize>(&self) -> PyResult<[Self::Target; N]> {
unreachable!()
}
}
Option::<NeverASequence<Self>>::None
}
/// Helper used to make a specialized path in extracting `DateTime<Tz>` where `Tz` is
/// `chrono::Local`, which will accept "naive" datetime objects as being in the local timezone.
#[cfg(feature = "chrono-local")]
#[inline]
fn as_local_tz(_: private::Token) -> Option<Self> {
None
}
}
mod from_py_object_sequence {
use crate::PyResult;
/// Private trait for implementing specialized sequence extraction for `Vec<u8>` and `[u8; N]`
#[doc(hidden)]
pub trait FromPyObjectSequence {
type Target;
fn to_vec(&self) -> Vec<Self::Target>;
fn to_array<const N: usize>(&self) -> PyResult<[Self::Target; N]>;
}
}
// Only reachable / implementable inside PyO3 itself.
pub(crate) use from_py_object_sequence::FromPyObjectSequence;
/// A data structure that can be extracted without borrowing any data from the input.
///
/// This is primarily useful for trait bounds. For example a [`FromPyObject`] implementation of a
/// wrapper type may be able to borrow data from the input, but a [`FromPyObject`] implementation of
/// a collection type may only extract owned data.
///
/// For example [`PyList`] will not hand out references tied to its own lifetime, but "owned"
/// references independent of it. (Similar to [`Vec<Arc<T>>`] where you clone the [`Arc<T>`] out).
/// This makes it impossible to collect borrowed types in a collection, since they would not borrow
/// from the original [`PyList`], but the much shorter lived element reference. See the example
/// below.
///
/// ```,no_run
/// # use pyo3::prelude::*;
/// # #[allow(dead_code)]
/// pub struct MyWrapper<T>(T);
///
/// impl<'a, 'py, T> FromPyObject<'a, 'py> for MyWrapper<T>
/// where
/// T: FromPyObject<'a, 'py>
/// {
/// type Error = T::Error;
///
/// fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
/// obj.extract().map(MyWrapper)
/// }
/// }
///
/// # #[allow(dead_code)]
/// pub struct MyVec<T>(Vec<T>);
///
/// impl<'py, T> FromPyObject<'_, 'py> for MyVec<T>
/// where
/// T: FromPyObjectOwned<'py> // 👈 can only extract owned values, because each `item` below
/// // is a temporary short lived owned reference
/// {
/// type Error = PyErr;
///
/// fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {
/// let mut v = MyVec(Vec::new());
/// for item in obj.try_iter()? {
/// v.0.push(item?.extract::<T>().map_err(Into::into)?);
/// }
/// Ok(v)
/// }
/// }
/// ```
///
/// [`PyList`]: crate::types::PyList
/// [`Arc<T>`]: alloc::sync::Arc
pub trait FromPyObjectOwned<'py>: for<'a> FromPyObject<'a, 'py> {}
impl<'py, T> FromPyObjectOwned<'py> for T where T: for<'a> FromPyObject<'a, 'py> {}
impl<'a, 'py, T> FromPyObject<'a, 'py> for T
where
T: PyClass + Clone + ExtractPyClassWithClone,
{
type Error = PyClassGuardError<'a, 'py>;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = <T as crate::PyTypeInfo>::TYPE_HINT;
fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
Ok(obj.extract::<PyClassGuard<'_, T>>()?.clone())
}
}
impl<'a, 'py, T> FromPyObject<'a, 'py> for PyRef<'py, T>
where
T: PyClass,
{
type Error = PyClassGuardError<'a, 'py>;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = <T as crate::PyTypeInfo>::TYPE_HINT;
fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
obj.cast::<T>()
.map_err(|e| PyClassGuardError(Some(e)))?
.try_borrow()
.map_err(|_| PyClassGuardError(None))
}
}
impl<'a, 'py, T> FromPyObject<'a, 'py> for PyRefMut<'py, T>
where
T: PyClass<Frozen = False>,
{
type Error = PyClassGuardMutError<'a, 'py>;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = <T as crate::PyTypeInfo>::TYPE_HINT;
fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
obj.cast::<T>()
.map_err(|e| PyClassGuardMutError(Some(e)))?
.try_borrow_mut()
.map_err(|_| PyClassGuardMutError(None))
}
}
impl<'py> IntoPyObject<'py> for () {
type Target = PyTuple;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr =
type_hint_subscript!(PyTuple::TYPE_HINT, PyStaticExpr::Tuple { elts: &[] });
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(PyTuple::empty(py))
}
}
/// ```rust,compile_fail
/// use pyo3::prelude::*;
///
/// #[pyclass]
/// struct TestClass {
/// num: u32,
/// }
///
/// let t = TestClass { num: 10 };
///
/// Python::attach(|py| {
/// let pyvalue = Py::new(py, t).unwrap().to_object(py);
/// let t: TestClass = pyvalue.extract(py).unwrap();
/// })
/// ```
mod test_no_clone {}
#[cfg(test)]
mod tests {
#[test]
#[cfg(feature = "macros")]
fn test_pyclass_skip_from_py_object() {
use crate::{types::PyAnyMethods, FromPyObject, IntoPyObject, PyErr, Python};
#[crate::pyclass(crate = "crate", skip_from_py_object)]
#[derive(Clone)]
struct Foo(i32);
impl<'py> FromPyObject<'_, 'py> for Foo {
type Error = PyErr;
fn extract(obj: crate::Borrowed<'_, 'py, crate::PyAny>) -> Result<Self, Self::Error> {
if let Ok(obj) = obj.cast::<Self>() {
Ok(obj.borrow().clone())
} else {
obj.extract::<i32>().map(Self)
}
}
}
Python::attach(|py| {
let foo1 = 42i32.into_pyobject(py)?;
assert_eq!(foo1.extract::<Foo>()?.0, 42);
let foo2 = Foo(0).into_pyobject(py)?;
assert_eq!(foo2.extract::<Foo>()?.0, 0);
Ok::<_, PyErr>(())
})
.unwrap();
}
#[test]
#[cfg(feature = "macros")]
fn test_pyclass_from_py_object() {
use crate::{types::PyAnyMethods, IntoPyObject, PyErr, Python};
#[crate::pyclass(crate = "crate", from_py_object)]
#[derive(Clone)]
struct Foo(i32);
Python::attach(|py| {
let foo1 = 42i32.into_pyobject(py)?;
assert!(foo1.extract::<Foo>().is_err());
let foo2 = Foo(0).into_pyobject(py)?;
assert_eq!(foo2.extract::<Foo>()?.0, 0);
Ok::<_, PyErr>(())
})
.unwrap();
}
}
+190
View File
@@ -0,0 +1,190 @@
#![cfg(feature = "anyhow")]
//! A conversion from [anyhow]’s [`Error`][anyhow-error] type to [`PyErr`].
//!
//! Use of an error handling library like [anyhow] is common in application code and when you just
//! want error handling to be easy. If you are writing a library or you need more control over your
//! errors you might want to design your own error type instead.
//!
//! When the inner error is a [`PyErr`] without source, it will be extracted out.
//! Otherwise a Python [`RuntimeError`] will be created.
//! You might find that you need to map the error from your Rust code into another Python exception.
//! See [`PyErr::new`] for more information about that.
//!
//! For information about error handling in general, see the [Error handling] chapter of the Rust
//! book.
//!
//! # Setup
//!
//! To use this feature, add this to your **`Cargo.toml`**:
//!
//! ```toml
//! [dependencies]
//! ## change * to the version you want to use, ideally the latest.
//! anyhow = "*"
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"anyhow\"] }")]
//! ```
//!
//! Note that you must use compatible versions of anyhow and PyO3.
//! The required anyhow version may vary based on the version of PyO3.
//!
//! # Example: Propagating a `PyErr` into [`anyhow::Error`]
//!
//! ```rust
//! use pyo3::prelude::*;
//! use std::path::PathBuf;
//!
//! // A wrapper around a Rust function.
//! // The pyfunction macro performs the conversion to a PyErr
//! #[pyfunction]
//! fn py_open(filename: PathBuf) -> anyhow::Result<Vec<u8>> {
//! let data = std::fs::read(filename)?;
//! Ok(data)
//! }
//!
//! fn main() {
//! let error = Python::attach(|py| -> PyResult<Vec<u8>> {
//! let fun = wrap_pyfunction!(py_open, py)?;
//! let text = fun.call1(("foo.txt",))?.extract::<Vec<u8>>()?;
//! Ok(text)
//! }).unwrap_err();
//!
//! println!("{}", error);
//! }
//! ```
//!
//! # Example: Using `anyhow` in general
//!
//! Note that you don't need this feature to convert a [`PyErr`] into an [`anyhow::Error`], because
//! it can already convert anything that implements [`Error`](core::error::Error):
//!
//! ```rust
//! use pyo3::prelude::*;
//! use pyo3::types::PyBytes;
//!
//! // An example function that must handle multiple error types.
//! //
//! // To do this you usually need to design your own error type or use
//! // `Box<dyn Error>`. `anyhow` is a convenient alternative for this.
//! pub fn decompress(bytes: &[u8]) -> anyhow::Result<String> {
//! // An arbitrary example of a Python api you
//! // could call inside an application...
//! // This might return a `PyErr`.
//! let res = Python::attach(|py| {
//! let zlib = PyModule::import(py, "zlib")?;
//! let decompress = zlib.getattr("decompress")?;
//! let bytes = PyBytes::new(py, bytes);
//! let value = decompress.call1((bytes,))?;
//! value.extract::<Vec<u8>>()
//! })?;
//!
//! // This might be a `FromUtf8Error`.
//! let text = String::from_utf8(res)?;
//!
//! Ok(text)
//! }
//!
//! fn main() -> anyhow::Result<()> {
//! let bytes: &[u8] = b"x\x9c\x8b\xcc/U(\xce\xc8/\xcdIQ((\xcaOJL\xca\xa9T\
//! (-NU(\xc9HU\xc8\xc9LJ\xcbI,IUH.\x02\x91\x99y\xc5%\
//! \xa9\x89)z\x00\xf2\x15\x12\xfe";
//! let text = decompress(bytes)?;
//!
//! println!("The text is \"{}\"", text);
//! # assert_eq!(text, "You should probably use the libflate crate instead.");
//! Ok(())
//! }
//! ```
//!
//! [anyhow]: https://docs.rs/anyhow/ "A trait object based error system for easy idiomatic error handling in Rust applications."
//! [anyhow-error]: https://docs.rs/anyhow/latest/anyhow/struct.Error.html "Anyhows `Error` type, a wrapper around a dynamic error type"
//! [`RuntimeError`]: https://docs.python.org/3/library/exceptions.html#RuntimeError "Built-in Exceptions — Python documentation"
//! [Error handling]: https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html "Recoverable Errors with Result - The Rust Programming Language"
use crate::exceptions::PyRuntimeError;
use crate::PyErr;
impl From<anyhow::Error> for PyErr {
fn from(mut error: anyhow::Error) -> Self {
// Errors containing a PyErr without chain or context are returned as the underlying error
if error.source().is_none() {
error = match error.downcast::<Self>() {
Ok(py_err) => return py_err,
Err(error) => error,
};
}
PyRuntimeError::new_err(format!("{error:?}"))
}
}
#[cfg(test)]
mod test_anyhow {
use crate::exceptions::{PyRuntimeError, PyValueError};
use crate::prelude::*;
use crate::types::IntoPyDict;
use anyhow::{anyhow, bail, Context, Result};
fn f() -> Result<()> {
use std::io;
bail!(io::Error::new(io::ErrorKind::PermissionDenied, "oh no!"));
}
fn g() -> Result<()> {
f().context("f failed")
}
fn h() -> Result<()> {
g().context("g failed")
}
#[test]
fn test_pyo3_exception_contents() {
let err = h().unwrap_err();
let expected_contents = format!("{err:?}");
let pyerr = PyErr::from(err);
Python::attach(|py| {
let locals = [("err", pyerr)].into_py_dict(py).unwrap();
let pyerr = py.run(c"raise err", None, Some(&locals)).unwrap_err();
assert_eq!(pyerr.value(py).to_string(), expected_contents);
})
}
fn k() -> Result<()> {
Err(anyhow!("Some sort of error"))
}
#[test]
fn test_pyo3_exception_contents2() {
let err = k().unwrap_err();
let expected_contents = format!("{err:?}");
let pyerr = PyErr::from(err);
Python::attach(|py| {
let locals = [("err", pyerr)].into_py_dict(py).unwrap();
let pyerr = py.run(c"raise err", None, Some(&locals)).unwrap_err();
assert_eq!(pyerr.value(py).to_string(), expected_contents);
})
}
#[test]
fn test_pyo3_unwrap_simple_err() {
let origin_exc = PyValueError::new_err("Value Error");
let err: anyhow::Error = origin_exc.into();
let converted: PyErr = err.into();
assert!(Python::attach(
|py| converted.is_instance_of::<PyValueError>(py)
))
}
#[test]
fn test_pyo3_unwrap_complex_err() {
let origin_exc = PyValueError::new_err("Value Error");
let mut err: anyhow::Error = origin_exc.into();
err = err.context("Context");
let converted: PyErr = err.into();
assert!(Python::attach(
|py| converted.is_instance_of::<PyRuntimeError>(py)
))
}
}
+264
View File
@@ -0,0 +1,264 @@
#![cfg(feature = "bigdecimal")]
//! Conversions to and from [bigdecimal](https://docs.rs/bigdecimal)'s [`BigDecimal`] type.
//!
//! This is useful for converting Python's decimal.Decimal into and from a native Rust type.
//!
//! # Setup
//!
//! To use this feature, add to your **`Cargo.toml`**:
//!
//! ```toml
//! [dependencies]
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"bigdecimal\"] }")]
//! bigdecimal = "0.4"
//! ```
//!
//! Note that you must use a compatible version of bigdecimal and PyO3.
//! The required bigdecimal version may vary based on the version of PyO3.
//!
//! # Example
//!
//! Rust code to create a function that adds one to a BigDecimal
//!
//! ```rust
//! use bigdecimal::BigDecimal;
//! use pyo3::prelude::*;
//!
//! #[pyfunction]
//! fn add_one(d: BigDecimal) -> BigDecimal {
//! d + 1
//! }
//!
//! #[pymodule]
//! fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
//! m.add_function(wrap_pyfunction!(add_one, m)?)?;
//! Ok(())
//! }
//! ```
//!
//! Python code that validates the functionality
//!
//!
//! ```python
//! from my_module import add_one
//! from decimal import Decimal
//!
//! d = Decimal("2")
//! value = add_one(d)
//!
//! assert d + 1 == value
//! ```
use core::str::FromStr;
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
#[cfg(feature = "experimental-inspect")]
use crate::type_hint_identifier;
use crate::types::PyTuple;
use crate::{
exceptions::PyValueError,
sync::PyOnceLock,
types::{PyAnyMethods, PyStringMethods, PyType},
Borrowed, Bound, FromPyObject, IntoPyObject, Py, PyAny, PyErr, PyResult, Python,
};
use bigdecimal::BigDecimal;
use num_bigint::Sign;
fn get_decimal_cls(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
static DECIMAL_CLS: PyOnceLock<Py<PyType>> = PyOnceLock::new();
DECIMAL_CLS.import(py, "decimal", "Decimal")
}
fn get_invalid_operation_error_cls(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
static INVALID_OPERATION_CLS: PyOnceLock<Py<PyType>> = PyOnceLock::new();
INVALID_OPERATION_CLS.import(py, "decimal", "InvalidOperation")
}
impl FromPyObject<'_, '_> for BigDecimal {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = type_hint_identifier!("decimal", "Decimal");
fn extract(obj: Borrowed<'_, '_, PyAny>) -> PyResult<Self> {
let py_str = &obj.str()?;
let rs_str = &py_str.to_cow()?;
BigDecimal::from_str(rs_str).map_err(|e| PyValueError::new_err(e.to_string()))
}
}
impl<'py> IntoPyObject<'py> for BigDecimal {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("decimal", "Decimal");
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let cls = get_decimal_cls(py)?;
let (bigint, scale) = self.into_bigint_and_scale();
if scale == 0 {
return cls.call1((bigint,));
}
let exponent = scale.checked_neg().ok_or_else(|| {
get_invalid_operation_error_cls(py)
.map_or_else(|err| err, |cls| PyErr::from_type(cls.clone(), ()))
})?;
let (sign, digits) = bigint.to_radix_be(10);
let signed = matches!(sign, Sign::Minus).into_pyobject(py)?;
let digits = PyTuple::new(py, digits)?;
cls.call1(((signed, digits, exponent),))
}
}
#[cfg(test)]
mod test_bigdecimal {
use super::*;
use crate::types::dict::PyDictMethods;
use crate::types::PyDict;
use alloc::ffi::CString;
use bigdecimal::{One, Zero};
#[cfg(not(target_arch = "wasm32"))]
use proptest::prelude::*;
macro_rules! convert_constants {
($name:ident, $rs:expr, $py:literal) => {
#[test]
fn $name() {
Python::attach(|py| {
let rs_orig = $rs;
let rs_dec = rs_orig.clone().into_pyobject(py).unwrap();
let locals = PyDict::new(py);
locals.set_item("rs_dec", &rs_dec).unwrap();
// Checks if BigDecimal -> Python Decimal conversion is correct
py.run(
&CString::new(format!(
"import decimal\npy_dec = decimal.Decimal(\"{}\")\nassert py_dec == rs_dec",
$py
))
.unwrap(),
None,
Some(&locals),
)
.unwrap();
// Checks if Python Decimal -> BigDecimal conversion is correct
let py_dec = locals.get_item("py_dec").unwrap().unwrap();
let py_result: BigDecimal = py_dec.extract().unwrap();
assert_eq!(rs_orig, py_result);
})
}
};
}
convert_constants!(convert_zero, BigDecimal::zero(), "0");
convert_constants!(convert_one, BigDecimal::one(), "1");
convert_constants!(convert_neg_one, -BigDecimal::one(), "-1");
convert_constants!(convert_two, BigDecimal::from(2), "2");
convert_constants!(convert_ten, BigDecimal::from_str("10").unwrap(), "10");
convert_constants!(
convert_one_hundred_point_one,
BigDecimal::from_str("100.1").unwrap(),
"100.1"
);
convert_constants!(
convert_one_thousand,
BigDecimal::from_str("1000").unwrap(),
"1000"
);
convert_constants!(
convert_scientific,
BigDecimal::from_str("1e10").unwrap(),
"1e10"
);
#[cfg(not(target_arch = "wasm32"))]
proptest! {
#[test]
fn test_roundtrip(
number in 0..28u32
) {
let num = BigDecimal::from(number);
Python::attach(|py| {
let rs_dec = num.clone().into_pyobject(py).unwrap();
let locals = PyDict::new(py);
locals.set_item("rs_dec", &rs_dec).unwrap();
py.run(
&CString::new(format!(
"import decimal\npy_dec = decimal.Decimal(\"{num}\")\nassert py_dec == rs_dec")).unwrap(),
None, Some(&locals)).unwrap();
let roundtripped: BigDecimal = rs_dec.extract().unwrap();
assert_eq!(num, roundtripped);
})
}
#[test]
fn test_integers(num in any::<i64>()) {
Python::attach(|py| {
let py_num = num.into_pyobject(py).unwrap();
let roundtripped: BigDecimal = py_num.extract().unwrap();
let rs_dec = BigDecimal::from(num);
assert_eq!(rs_dec, roundtripped);
})
}
}
#[test]
fn test_nan() {
Python::attach(|py| {
let locals = PyDict::new(py);
py.run(
c"import decimal\npy_dec = decimal.Decimal(\"NaN\")",
None,
Some(&locals),
)
.unwrap();
let py_dec = locals.get_item("py_dec").unwrap().unwrap();
let roundtripped: Result<BigDecimal, PyErr> = py_dec.extract();
assert!(roundtripped.is_err());
})
}
#[test]
fn test_infinity() {
Python::attach(|py| {
let locals = PyDict::new(py);
py.run(
c"import decimal\npy_dec = decimal.Decimal(\"Infinity\")",
None,
Some(&locals),
)
.unwrap();
let py_dec = locals.get_item("py_dec").unwrap().unwrap();
let roundtripped: Result<BigDecimal, PyErr> = py_dec.extract();
assert!(roundtripped.is_err());
})
}
#[test]
fn test_no_precision_loss() {
Python::attach(|py| {
let src = "1e4";
let expected = get_decimal_cls(py)
.unwrap()
.call1((src,))
.unwrap()
.call_method0("as_tuple")
.unwrap();
let actual = src
.parse::<BigDecimal>()
.unwrap()
.into_pyobject(py)
.unwrap()
.call_method0("as_tuple")
.unwrap();
assert!(actual.eq(expected).unwrap());
});
}
}
+147
View File
@@ -0,0 +1,147 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
#![cfg(feature = "bytes")]
//! Conversions to and from [bytes](https://docs.rs/bytes/latest/bytes/)'s [`Bytes`].
//!
//! This is useful for efficiently converting Python's `bytes` types efficiently.
//! While `bytes` will be directly borrowed, converting from `bytearray` will result in a copy.
//!
//! When converting `Bytes` back into Python, this will do a copy, just like `&[u8]` and `Vec<u8>`.
//!
//! # When to use `Bytes`
//!
//! Unless you specifically need [`Bytes`] for ref-counted ownership and sharing,
//! you may find that using `&[u8]`, `Vec<u8>`, [`Bound<PyBytes>`], or [`PyBackedBytes`]
//! is simpler for most use cases.
//!
//! # Setup
//!
//! To use this feature, add in your **`Cargo.toml`**:
//!
//! ```toml
//! [dependencies]
//! bytes = "1.10"
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"bytes\"] }")]
//! ```
//!
//! Note that you must use compatible versions of bytes and PyO3.
//!
//! # Example
//!
//! Rust code to create functions which return `Bytes` or take `Bytes` as arguments:
//!
//! ```rust,no_run
//! use pyo3::prelude::*;
//! use bytes::Bytes;
//!
//! #[pyfunction]
//! fn get_message_bytes() -> Bytes {
//! Bytes::from_static(b"Hello Python!")
//! }
//!
//! #[pyfunction]
//! fn num_bytes(bytes: Bytes) -> usize {
//! bytes.len()
//! }
//!
//! #[pymodule]
//! fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
//! m.add_function(wrap_pyfunction!(get_message_bytes, m)?)?;
//! m.add_function(wrap_pyfunction!(num_bytes, m)?)?;
//! Ok(())
//! }
//! ```
//!
//! Python code that calls these functions:
//!
//! ```python
//! from my_module import get_message_bytes, num_bytes
//!
//! message = get_message_bytes()
//! assert message == b"Hello Python!"
//!
//! size = num_bytes(message)
//! assert size == 13
//! ```
use bytes::Bytes;
use crate::conversion::IntoPyObject;
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
use crate::instance::Bound;
use crate::pybacked::PyBackedBytes;
use crate::types::PyBytes;
#[cfg(feature = "experimental-inspect")]
use crate::PyTypeInfo;
use crate::{Borrowed, CastError, FromPyObject, PyAny, PyErr, Python};
impl<'a, 'py> FromPyObject<'a, 'py> for Bytes {
type Error = CastError<'a, 'py>;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = PyBackedBytes::INPUT_TYPE;
fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
Ok(Bytes::from_owner(obj.extract::<PyBackedBytes>()?))
}
}
impl<'py> IntoPyObject<'py> for Bytes {
type Target = PyBytes;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = PyBytes::TYPE_HINT;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(PyBytes::new(py, &self))
}
}
impl<'py> IntoPyObject<'py> for &Bytes {
type Target = PyBytes;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = PyBytes::TYPE_HINT;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(PyBytes::new(py, self))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{PyAnyMethods, PyByteArray, PyByteArrayMethods, PyBytes};
use crate::Python;
#[test]
fn test_bytes() {
Python::attach(|py| {
let py_bytes = PyBytes::new(py, b"foobar");
let bytes: Bytes = py_bytes.extract().unwrap();
assert_eq!(&*bytes, b"foobar");
let bytes = Bytes::from_static(b"foobar").into_pyobject(py).unwrap();
assert!(bytes.is_instance_of::<PyBytes>());
});
}
#[test]
fn test_bytearray() {
Python::attach(|py| {
let py_bytearray = PyByteArray::new(py, b"foobar");
let bytes: Bytes = py_bytearray.extract().unwrap();
assert_eq!(&*bytes, b"foobar");
// Editing the bytearray should not change extracted Bytes
unsafe { py_bytearray.as_bytes_mut()[0] = b'x' };
assert_eq!(&bytes, "foobar");
assert_eq!(&py_bytearray.extract::<Vec<u8>>().unwrap(), b"xoobar");
});
}
}
File diff suppressed because it is too large. Load diff
+229
View File
@@ -0,0 +1,229 @@
#![cfg(all(Py_3_9, feature = "chrono-tz"))]
//! Conversions to and from [chrono-tz](https://docs.rs/chrono-tz/)’s `Tz`.
//!
//! This feature requires at least Python 3.9.
//!
//! # Setup
//!
//! To use this feature, add this to your **`Cargo.toml`**:
//!
//! ```toml
//! [dependencies]
//! chrono-tz = "0.8"
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"chrono-tz\"] }")]
//! ```
//!
//! Note that you must use compatible versions of chrono, chrono-tz and PyO3.
//! The required chrono version may vary based on the version of PyO3.
//!
//! # Example: Convert a `zoneinfo.ZoneInfo` to chrono-tz's `Tz`
//!
//! ```rust,no_run
//! use chrono_tz::Tz;
//! use pyo3::{Python, PyResult, IntoPyObject, types::PyAnyMethods};
//!
//! fn main() -> PyResult<()> {
//! Python::initialize();
//! Python::attach(|py| {
//! // Convert to Python
//! let py_tzinfo = Tz::Europe__Paris.into_pyobject(py)?;
//! // Convert back to Rust
//! assert_eq!(py_tzinfo.extract::<Tz>()?, Tz::Europe__Paris);
//! Ok(())
//! })
//! }
//! ```
use crate::conversion::IntoPyObject;
use crate::exceptions::PyValueError;
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
#[cfg(feature = "experimental-inspect")]
use crate::type_hint_identifier;
use crate::types::{any::PyAnyMethods, PyTzInfo};
#[cfg(all(feature = "experimental-inspect", not(Py_3_9)))]
use crate::PyTypeInfo;
use crate::{intern, Borrowed, Bound, FromPyObject, PyAny, PyErr, Python};
use alloc::borrow::Cow;
use chrono_tz::Tz;
use core::str::FromStr;
impl<'py> IntoPyObject<'py> for Tz {
type Target = PyTzInfo;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(all(feature = "experimental-inspect", Py_3_9))]
const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("zoneinfo", "ZoneInfo");
#[cfg(all(feature = "experimental-inspect", not(Py_3_9)))]
const OUTPUT_TYPE: PyStaticExpr = PyTzInfo::TYPE_HINT;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
PyTzInfo::timezone(py, self.name())
}
}
impl<'py> IntoPyObject<'py> for &Tz {
type Target = PyTzInfo;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = Tz::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
impl FromPyObject<'_, '_> for Tz {
type Error = PyErr;
#[cfg(all(feature = "experimental-inspect", Py_3_9))]
const INPUT_TYPE: PyStaticExpr = type_hint_identifier!("zoneinfo", "ZoneInfo");
#[cfg(all(feature = "experimental-inspect", not(Py_3_9)))]
const INPUT_TYPE: PyStaticExpr = PyTzInfo::TYPE_HINT;
fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
Tz::from_str(
&ob.getattr(intern!(ob.py(), "key"))?
.extract::<Cow<'_, str>>()?,
)
.map_err(|e| PyValueError::new_err(e.to_string()))
}
}
#[cfg(all(test, not(windows)))] // Troubles loading timezones on Windows
mod tests {
use super::*;
use crate::prelude::PyAnyMethods;
use crate::types::IntoPyDict;
use crate::types::PyTzInfo;
use crate::Bound;
use crate::Python;
use chrono::offset::LocalResult;
use chrono::NaiveDate;
use chrono::{DateTime, Utc};
use chrono_tz::Tz;
#[test]
fn test_frompyobject() {
Python::attach(|py| {
assert_eq!(
new_zoneinfo(py, "Europe/Paris").extract::<Tz>().unwrap(),
Tz::Europe__Paris
);
assert_eq!(new_zoneinfo(py, "UTC").extract::<Tz>().unwrap(), Tz::UTC);
assert_eq!(
new_zoneinfo(py, "Etc/GMT-5").extract::<Tz>().unwrap(),
Tz::Etc__GMTMinus5
);
});
}
#[test]
fn test_ambiguous_datetime_to_pyobject() {
let dates = [
DateTime::<Utc>::from_str("2020-10-24 23:00:00 UTC").unwrap(),
DateTime::<Utc>::from_str("2020-10-25 00:00:00 UTC").unwrap(),
DateTime::<Utc>::from_str("2020-10-25 01:00:00 UTC").unwrap(),
];
let dates = dates.map(|dt| dt.with_timezone(&Tz::Europe__London));
assert_eq!(
dates.map(|dt| dt.to_string()),
[
"2020-10-25 00:00:00 BST",
"2020-10-25 01:00:00 BST",
"2020-10-25 01:00:00 GMT"
]
);
let dates = Python::attach(|py| {
let pydates = dates.map(|dt| dt.into_pyobject(py).unwrap());
assert_eq!(
pydates
.clone()
.map(|dt| dt.getattr("hour").unwrap().extract::<usize>().unwrap()),
[0, 1, 1]
);
assert_eq!(
pydates
.clone()
.map(|dt| dt.getattr("fold").unwrap().extract::<usize>().unwrap() > 0),
[false, false, true]
);
pydates.map(|dt| dt.extract::<DateTime<Tz>>().unwrap())
});
assert_eq!(
dates.map(|dt| dt.to_string()),
[
"2020-10-25 00:00:00 BST",
"2020-10-25 01:00:00 BST",
"2020-10-25 01:00:00 GMT"
]
);
}
#[test]
fn test_nonexistent_datetime_from_pyobject() {
// Pacific_Apia skipped the 30th of December 2011 entirely
let naive_dt = NaiveDate::from_ymd_opt(2011, 12, 30)
.unwrap()
.and_hms_opt(2, 0, 0)
.unwrap();
let tz = Tz::Pacific__Apia;
// sanity check
assert_eq!(naive_dt.and_local_timezone(tz), LocalResult::None);
Python::attach(|py| {
// create as a Python object manually
let py_tz = tz.into_pyobject(py).unwrap();
let py_dt_naive = naive_dt.into_pyobject(py).unwrap();
let py_dt = py_dt_naive
.call_method(
"replace",
(),
Some(&[("tzinfo", py_tz)].into_py_dict(py).unwrap()),
)
.unwrap();
// now try to extract
let err = py_dt.extract::<DateTime<Tz>>().unwrap_err();
assert_eq!(err.to_string(), "ValueError: The datetime datetime.datetime(2011, 12, 30, 2, 0, tzinfo=zoneinfo.ZoneInfo(key='Pacific/Apia')) contains an incompatible timezone");
});
}
#[test]
#[cfg(not(Py_GIL_DISABLED))] // https://github.com/python/cpython/issues/116738#issuecomment-2404360445
fn test_into_pyobject() {
Python::attach(|py| {
let assert_eq = |l: Bound<'_, PyTzInfo>, r: Bound<'_, PyTzInfo>| {
assert!(l.eq(&r).unwrap(), "{l:?} != {r:?}");
};
assert_eq(
Tz::Europe__Paris.into_pyobject(py).unwrap(),
new_zoneinfo(py, "Europe/Paris"),
);
assert_eq(Tz::UTC.into_pyobject(py).unwrap(), new_zoneinfo(py, "UTC"));
assert_eq(
Tz::Etc__GMTMinus5.into_pyobject(py).unwrap(),
new_zoneinfo(py, "Etc/GMT-5"),
);
});
}
fn new_zoneinfo<'py>(py: Python<'py>, name: &str) -> Bound<'py, PyTzInfo> {
PyTzInfo::timezone(py, name).unwrap()
}
}
+173
View File
@@ -0,0 +1,173 @@
#![cfg(feature = "either")]
//! Conversion to/from
//! [either](https://docs.rs/either/ "A library for easy idiomatic error handling and reporting in Rust applications")’s
//! [`Either`] type to a union of two Python types.
//!
//! Use of a generic sum type like [either] is common when you want to either accept one of two possible
//! types as an argument or return one of two possible types from a function, without having to define
//! a helper type manually yourself.
//!
//! # Setup
//!
//! To use this feature, add this to your **`Cargo.toml`**:
//!
//! ```toml
//! [dependencies]
//! ## change * to the version you want to use, ideally the latest.
//! either = "*"
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"either\"] }")]
//! ```
//!
//! Note that you must use compatible versions of either and PyO3.
//! The required either version may vary based on the version of PyO3.
//!
//! # Example: Convert a `int | str` to `Either<i32, String>`.
//!
//! ```rust
//! use either::Either;
//! use pyo3::{Python, PyResult, IntoPyObject, types::PyAnyMethods};
//!
//! fn main() -> PyResult<()> {
//! Python::initialize();
//! Python::attach(|py| {
//! // Create a string and an int in Python.
//! let py_str = "crab".into_pyobject(py)?;
//! let py_int = 42i32.into_pyobject(py)?;
//! // Now convert it to an Either<i32, String>.
//! let either_str: Either<i32, String> = py_str.extract()?;
//! let either_int: Either<i32, String> = py_int.extract()?;
//! Ok(())
//! })
//! }
//! ```
//!
//! [either](https://docs.rs/either/ "A library for easy idiomatic error handling and reporting in Rust applications")’s
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
#[cfg(feature = "experimental-inspect")]
use crate::type_hint_union;
use crate::{
exceptions::PyTypeError, Borrowed, Bound, FromPyObject, IntoPyObject, IntoPyObjectExt, PyAny,
PyErr, Python,
};
use either::Either;
#[cfg_attr(docsrs, doc(cfg(feature = "either")))]
impl<'py, L, R> IntoPyObject<'py> for Either<L, R>
where
L: IntoPyObject<'py>,
R: IntoPyObject<'py>,
{
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = type_hint_union!(L::OUTPUT_TYPE, R::OUTPUT_TYPE);
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
match self {
Either::Left(l) => l.into_bound_py_any(py),
Either::Right(r) => r.into_bound_py_any(py),
}
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "either")))]
impl<'a, 'py, L, R> IntoPyObject<'py> for &'a Either<L, R>
where
&'a L: IntoPyObject<'py>,
&'a R: IntoPyObject<'py>,
{
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = type_hint_union!(<&L>::OUTPUT_TYPE, <&R>::OUTPUT_TYPE);
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
match self {
Either::Left(l) => l.into_bound_py_any(py),
Either::Right(r) => r.into_bound_py_any(py),
}
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "either")))]
impl<'a, 'py, L, R> FromPyObject<'a, 'py> for Either<L, R>
where
L: FromPyObject<'a, 'py>,
R: FromPyObject<'a, 'py>,
{
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = type_hint_union!(L::INPUT_TYPE, R::INPUT_TYPE);
#[inline]
fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
if let Ok(l) = obj.extract::<L>() {
Ok(Either::Left(l))
} else if let Ok(r) = obj.extract::<R>() {
Ok(Either::Right(r))
} else {
// TODO: it might be nice to use the `type_input()` name here once `type_input`
// is not experimental, rather than the Rust type names.
let err_msg = format!(
"failed to convert the value to 'Union[{}, {}]'",
core::any::type_name::<L>(),
core::any::type_name::<R>()
);
Err(PyTypeError::new_err(err_msg))
}
}
}
#[cfg(test)]
mod tests {
use alloc::borrow::Cow;
use crate::exceptions::PyTypeError;
use crate::{IntoPyObject, Python};
use crate::types::PyAnyMethods;
use either::Either;
#[test]
fn test_either_conversion() {
type E = Either<i32, String>;
type E1 = Either<i32, f32>;
type E2 = Either<f32, i32>;
Python::attach(|py| {
let l = E::Left(42);
let obj_l = (&l).into_pyobject(py).unwrap();
assert_eq!(obj_l.extract::<i32>().unwrap(), 42);
assert_eq!(obj_l.extract::<E>().unwrap(), l);
let r = E::Right("foo".to_owned());
let obj_r = (&r).into_pyobject(py).unwrap();
assert_eq!(obj_r.extract::<Cow<'_, str>>().unwrap(), "foo");
assert_eq!(obj_r.extract::<E>().unwrap(), r);
let obj_s = "foo".into_pyobject(py).unwrap();
let err = obj_s.extract::<E1>().unwrap_err();
assert!(err.is_instance_of::<PyTypeError>(py));
assert_eq!(
err.to_string(),
"TypeError: failed to convert the value to 'Union[i32, f32]'"
);
let obj_i = 42i32.into_pyobject(py).unwrap();
assert_eq!(obj_i.extract::<E1>().unwrap(), E1::Left(42));
assert_eq!(obj_i.extract::<E2>().unwrap(), E2::Left(42.0));
let obj_f = 42.0f64.into_pyobject(py).unwrap();
assert_eq!(obj_f.extract::<E1>().unwrap(), E1::Right(42.0));
assert_eq!(obj_f.extract::<E2>().unwrap(), E2::Left(42.0));
});
}
}
+196
View File
@@ -0,0 +1,196 @@
#![cfg(feature = "eyre")]
//! A conversion from
//! [eyre](https://docs.rs/eyre/ "A library for easy idiomatic error handling and reporting in Rust applications.")’s
//! [`Report`] type to [`PyErr`].
//!
//! Use of an error handling library like [eyre] is common in application code and when you just
//! want error handling to be easy. If you are writing a library or you need more control over your
//! errors you might want to design your own error type instead.
//!
//! When the inner error is a [`PyErr`] without source, it will be extracted out.
//! Otherwise a Python [`RuntimeError`] will be created.
//! You might find that you need to map the error from your Rust code into another Python exception.
//! See [`PyErr::new`] for more information about that.
//!
//! For information about error handling in general, see the [Error handling] chapter of the Rust
//! book.
//!
//! # Setup
//!
//! To use this feature, add this to your **`Cargo.toml`**:
//!
//! ```toml
//! [dependencies]
//! ## change * to the version you want to use, ideally the latest.
//! eyre = "*"
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"eyre\"] }")]
//! ```
//!
//! Note that you must use compatible versions of eyre and PyO3.
//! The required eyre version may vary based on the version of PyO3.
//!
//! # Example: Propagating a `PyErr` into [`eyre::Report`]
//!
//! ```rust
//! use pyo3::prelude::*;
//! use std::path::PathBuf;
//!
//! // A wrapper around a Rust function.
//! // The pyfunction macro performs the conversion to a PyErr
//! #[pyfunction]
//! fn py_open(filename: PathBuf) -> eyre::Result<Vec<u8>> {
//! let data = std::fs::read(filename)?;
//! Ok(data)
//! }
//!
//! fn main() {
//! let error = Python::attach(|py| -> PyResult<Vec<u8>> {
//! let fun = wrap_pyfunction!(py_open, py)?;
//! let text = fun.call1(("foo.txt",))?.extract::<Vec<u8>>()?;
//! Ok(text)
//! }).unwrap_err();
//!
//! println!("{}", error);
//! }
//! ```
//!
//! # Example: Using `eyre` in general
//!
//! Note that you don't need this feature to convert a [`PyErr`] into an [`eyre::Report`], because
//! it can already convert anything that implements [`Error`](core::error::Error):
//!
//! ```rust
//! use pyo3::prelude::*;
//! use pyo3::types::PyBytes;
//!
//! // An example function that must handle multiple error types.
//! //
//! // To do this you usually need to design your own error type or use
//! // `Box<dyn Error>`. `eyre` is a convenient alternative for this.
//! pub fn decompress(bytes: &[u8]) -> eyre::Result<String> {
//! // An arbitrary example of a Python api you
//! // could call inside an application...
//! // This might return a `PyErr`.
//! let res = Python::attach(|py| {
//! let zlib = PyModule::import(py, "zlib")?;
//! let decompress = zlib.getattr("decompress")?;
//! let bytes = PyBytes::new(py, bytes);
//! let value = decompress.call1((bytes,))?;
//! value.extract::<Vec<u8>>()
//! })?;
//!
//! // This might be a `FromUtf8Error`.
//! let text = String::from_utf8(res)?;
//!
//! Ok(text)
//! }
//!
//! fn main() -> eyre::Result<()> {
//! let bytes: &[u8] = b"x\x9c\x8b\xcc/U(\xce\xc8/\xcdIQ((\xcaOJL\xca\xa9T\
//! (-NU(\xc9HU\xc8\xc9LJ\xcbI,IUH.\x02\x91\x99y\xc5%\
//! \xa9\x89)z\x00\xf2\x15\x12\xfe";
//! let text = decompress(bytes)?;
//!
//! println!("The text is \"{}\"", text);
//! # assert_eq!(text, "You should probably use the libflate crate instead.");
//! Ok(())
//! }
//! ```
//!
//! [eyre]: https://docs.rs/eyre/ "A library for easy idiomatic error handling and reporting in Rust applications."
//! [`RuntimeError`]: https://docs.python.org/3/library/exceptions.html#RuntimeError "Built-in Exceptions — Python documentation"
//! [Error handling]: https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html "Recoverable Errors with Result - The Rust Programming Language"
use crate::exceptions::PyRuntimeError;
use crate::PyErr;
use eyre::Report;
/// Converts [`eyre::Report`] to a [`PyErr`] containing a [`PyRuntimeError`].
///
/// If you want to raise a different Python exception you will have to do so manually. See
/// [`PyErr::new`] for more information about that.
impl From<eyre::Report> for PyErr {
fn from(mut error: Report) -> Self {
// Errors containing a PyErr without chain or context are returned as the underlying error
if error.source().is_none() {
error = match error.downcast::<Self>() {
Ok(py_err) => return py_err,
Err(error) => error,
};
}
PyRuntimeError::new_err(format!("{error:?}"))
}
}
#[cfg(test)]
mod tests {
use crate::exceptions::{PyRuntimeError, PyValueError};
use crate::prelude::*;
use crate::types::IntoPyDict;
use eyre::{bail, eyre, Report, Result, WrapErr};
fn f() -> Result<()> {
use std::io;
bail!(io::Error::new(io::ErrorKind::PermissionDenied, "oh no!"));
}
fn g() -> Result<()> {
f().wrap_err("f failed")
}
fn h() -> Result<()> {
g().wrap_err("g failed")
}
#[test]
fn test_pyo3_exception_contents() {
let err = h().unwrap_err();
let expected_contents = format!("{err:?}");
let pyerr = PyErr::from(err);
Python::attach(|py| {
let locals = [("err", pyerr)].into_py_dict(py).unwrap();
let pyerr = py.run(c"raise err", None, Some(&locals)).unwrap_err();
assert_eq!(pyerr.value(py).to_string(), expected_contents);
})
}
fn k() -> Result<()> {
Err(eyre!("Some sort of error"))
}
#[test]
fn test_pyo3_exception_contents2() {
let err = k().unwrap_err();
let expected_contents = format!("{err:?}");
let pyerr = PyErr::from(err);
Python::attach(|py| {
let locals = [("err", pyerr)].into_py_dict(py).unwrap();
let pyerr = py.run(c"raise err", None, Some(&locals)).unwrap_err();
assert_eq!(pyerr.value(py).to_string(), expected_contents);
})
}
#[test]
fn test_pyo3_unwrap_simple_err() {
let origin_exc = PyValueError::new_err("Value Error");
let report: Report = origin_exc.into();
let converted: PyErr = report.into();
assert!(Python::attach(
|py| converted.is_instance_of::<PyValueError>(py)
))
}
#[test]
fn test_pyo3_unwrap_complex_err() {
let origin_exc = PyValueError::new_err("Value Error");
let mut report: Report = origin_exc.into();
report = report.wrap_err("Wrapped");
let converted: PyErr = report.into();
assert!(Python::attach(
|py| converted.is_instance_of::<PyRuntimeError>(py)
))
}
}
+246
View File
@@ -0,0 +1,246 @@
#![cfg(feature = "hashbrown")]
//! Conversions to and from [hashbrown](https://docs.rs/hashbrown/)’s
//! `HashMap` and `HashSet`.
//!
//! # Setup
//!
//! To use this feature, add this to your **`Cargo.toml`**:
//!
//! ```toml
//! [dependencies]
//! # change * to the latest versions
//! hashbrown = "*"
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"hashbrown\"] }")]
//! ```
//!
//! Note that you must use compatible versions of hashbrown and PyO3.
//! The required hashbrown version may vary based on the version of PyO3.
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
use crate::{
conversion::{FromPyObjectOwned, IntoPyObject},
types::{
any::PyAnyMethods, dict::PyDictMethods, frozenset::PyFrozenSetMethods, set::PySetMethods,
PyDict, PyFrozenSet, PySet,
},
Borrowed, Bound, FromPyObject, PyAny, PyErr, PyResult, Python,
};
#[cfg(feature = "experimental-inspect")]
use crate::{type_hint_subscript, type_hint_union, PyTypeInfo};
use core::hash;
impl<'py, K, V, H> IntoPyObject<'py> for hashbrown::HashMap<K, V, H>
where
K: IntoPyObject<'py> + Eq + hash::Hash,
V: IntoPyObject<'py>,
H: hash::BuildHasher,
{
type Target = PyDict;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr =
type_hint_subscript!(PyDict::TYPE_HINT, K::OUTPUT_TYPE, V::OUTPUT_TYPE);
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let dict = PyDict::new(py);
for (k, v) in self {
dict.set_item(k, v)?;
}
Ok(dict)
}
}
impl<'a, 'py, K, V, H> IntoPyObject<'py> for &'a hashbrown::HashMap<K, V, H>
where
&'a K: IntoPyObject<'py> + Eq + hash::Hash,
&'a V: IntoPyObject<'py>,
H: hash::BuildHasher,
{
type Target = PyDict;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr =
type_hint_subscript!(PyDict::TYPE_HINT, <&K>::OUTPUT_TYPE, <&V>::OUTPUT_TYPE);
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let dict = PyDict::new(py);
for (k, v) in self {
dict.set_item(k, v)?;
}
Ok(dict)
}
}
impl<'py, K, V, S> FromPyObject<'_, 'py> for hashbrown::HashMap<K, V, S>
where
K: FromPyObjectOwned<'py> + Eq + hash::Hash,
V: FromPyObjectOwned<'py>,
S: hash::BuildHasher + Default,
{
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr =
type_hint_subscript!(PyDict::TYPE_HINT, K::INPUT_TYPE, V::INPUT_TYPE);
fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result<Self, PyErr> {
let dict = ob.cast::<PyDict>()?;
let mut ret = hashbrown::HashMap::with_capacity_and_hasher(dict.len(), S::default());
for (k, v) in dict.iter() {
ret.insert(
k.extract().map_err(Into::into)?,
v.extract().map_err(Into::into)?,
);
}
Ok(ret)
}
}
impl<'py, K, H> IntoPyObject<'py> for hashbrown::HashSet<K, H>
where
K: IntoPyObject<'py> + Eq + hash::Hash,
H: hash::BuildHasher,
{
type Target = PySet;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = type_hint_subscript!(PySet::TYPE_HINT, K::OUTPUT_TYPE);
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
PySet::new(py, self)
}
}
impl<'a, 'py, K, H> IntoPyObject<'py> for &'a hashbrown::HashSet<K, H>
where
&'a K: IntoPyObject<'py> + Eq + hash::Hash,
H: hash::BuildHasher,
{
type Target = PySet;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = type_hint_subscript!(PySet::TYPE_HINT, <&K>::OUTPUT_TYPE);
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
PySet::new(py, self)
}
}
impl<'py, K, S> FromPyObject<'_, 'py> for hashbrown::HashSet<K, S>
where
K: FromPyObjectOwned<'py> + Eq + hash::Hash,
S: hash::BuildHasher + Default,
{
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = type_hint_union!(
type_hint_subscript!(PySet::TYPE_HINT, K::INPUT_TYPE),
type_hint_subscript!(PyFrozenSet::TYPE_HINT, K::INPUT_TYPE)
);
fn extract(ob: Borrowed<'_, 'py, PyAny>) -> PyResult<Self> {
match ob.cast::<PySet>() {
Ok(set) => set
.iter()
.map(|any| any.extract().map_err(Into::into))
.collect(),
Err(err) => {
if let Ok(frozen_set) = ob.cast::<PyFrozenSet>() {
frozen_set
.iter()
.map(|any| any.extract().map_err(Into::into))
.collect()
} else {
Err(PyErr::from(err))
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::IntoPyDict;
use std::collections::hash_map::RandomState;
#[test]
fn test_hashbrown_hashmap_into_pyobject() {
Python::attach(|py| {
let mut map =
hashbrown::HashMap::<i32, i32, RandomState>::with_hasher(RandomState::new());
map.insert(1, 1);
let py_map = (&map).into_pyobject(py).unwrap();
assert_eq!(py_map.len(), 1);
assert!(
py_map
.get_item(1)
.unwrap()
.unwrap()
.extract::<i32>()
.unwrap()
== 1
);
assert_eq!(map, py_map.extract().unwrap());
});
}
#[test]
fn test_hashbrown_hashmap_into_dict() {
Python::attach(|py| {
let mut map =
hashbrown::HashMap::<i32, i32, RandomState>::with_hasher(RandomState::new());
map.insert(1, 1);
let py_map = map.into_py_dict(py).unwrap();
assert_eq!(py_map.len(), 1);
assert_eq!(
py_map
.get_item(1)
.unwrap()
.unwrap()
.extract::<i32>()
.unwrap(),
1
);
});
}
#[test]
fn test_extract_hashbrown_hashset() {
Python::attach(|py| {
let set = PySet::new(py, [1, 2, 3, 4, 5]).unwrap();
let hash_set: hashbrown::HashSet<usize, RandomState> = set.extract().unwrap();
assert_eq!(hash_set, [1, 2, 3, 4, 5].iter().copied().collect());
let set = PyFrozenSet::new(py, [1, 2, 3, 4, 5]).unwrap();
let hash_set: hashbrown::HashSet<usize, RandomState> = set.extract().unwrap();
assert_eq!(hash_set, [1, 2, 3, 4, 5].iter().copied().collect());
});
}
#[test]
fn test_hashbrown_hashset_into_pyobject() {
Python::attach(|py| {
let hs: hashbrown::HashSet<u64, RandomState> =
[1, 2, 3, 4, 5].iter().cloned().collect();
let hso = hs.clone().into_pyobject(py).unwrap();
assert_eq!(hs, hso.extract().unwrap());
});
}
}
+251
View File
@@ -0,0 +1,251 @@
#![cfg(feature = "indexmap")]
//! Conversions to and from [indexmap](https://docs.rs/indexmap/)’s
//! `IndexMap`.
//!
//! [`indexmap::IndexMap`] is a hash table that is closely compatible with the standard [`std::collections::HashMap`],
//! with the difference that it preserves the insertion order when iterating over keys. It was inspired
//! by Python's 3.6+ dict implementation.
//!
//! Dictionary order is guaranteed to be insertion order in Python, hence IndexMap is a good candidate
//! for maintaining an equivalent behaviour in Rust.
//!
//! # Setup
//!
//! To use this feature, add this to your **`Cargo.toml`**:
//!
//! ```toml
//! [dependencies]
//! # change * to the latest versions
//! indexmap = "*"
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"indexmap\"] }")]
//! ```
//!
//! Note that you must use compatible versions of indexmap and PyO3.
//! The required indexmap version may vary based on the version of PyO3.
//!
//! # Examples
//!
//! Using [indexmap](https://docs.rs/indexmap) to return a dictionary with some statistics
//! about a list of numbers. Because of the insertion order guarantees, the Python code will
//! always print the same result, matching users' expectations about Python's dict.
//! ```rust
//! use indexmap::{indexmap, IndexMap};
//! use pyo3::prelude::*;
//!
//! fn median(data: &Vec<i32>) -> f32 {
//! let sorted_data = data.clone().sort();
//! let mid = data.len() / 2;
//! if data.len() % 2 == 0 {
//! data[mid] as f32
//! }
//! else {
//! (data[mid] + data[mid - 1]) as f32 / 2.0
//! }
//! }
//!
//! fn mean(data: &Vec<i32>) -> f32 {
//! data.iter().sum::<i32>() as f32 / data.len() as f32
//! }
//! fn mode(data: &Vec<i32>) -> f32 {
//! let mut frequency = IndexMap::new(); // we can use IndexMap as any hash table
//!
//! for &element in data {
//! *frequency.entry(element).or_insert(0) += 1;
//! }
//!
//! frequency
//! .iter()
//! .max_by(|a, b| a.1.cmp(&b.1))
//! .map(|(k, _v)| *k)
//! .unwrap() as f32
//! }
//!
//! #[pyfunction]
//! fn calculate_statistics(data: Vec<i32>) -> IndexMap<&'static str, f32> {
//! indexmap! {
//! "median" => median(&data),
//! "mean" => mean(&data),
//! "mode" => mode(&data),
//! }
//! }
//!
//! #[pymodule]
//! fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
//! m.add_function(wrap_pyfunction!(calculate_statistics, m)?)?;
//! Ok(())
//! }
//! ```
//!
//! Python code:
//! ```python
//! from my_module import calculate_statistics
//!
//! data = [1, 1, 1, 3, 4, 5]
//! print(calculate_statistics(data))
//! # always prints {"median": 2.0, "mean": 2.5, "mode": 1.0} in the same order
//! # if another hash table was used, the order could be random
//! ```
use crate::conversion::{FromPyObjectOwned, IntoPyObject};
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
use crate::types::*;
#[cfg(feature = "experimental-inspect")]
use crate::{type_hint_subscript, PyTypeInfo};
use crate::{Borrowed, Bound, FromPyObject, PyErr, Python};
use core::hash;
impl<'py, K, V, H> IntoPyObject<'py> for indexmap::IndexMap<K, V, H>
where
K: IntoPyObject<'py> + Eq + hash::Hash,
V: IntoPyObject<'py>,
H: hash::BuildHasher,
{
type Target = PyDict;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr =
type_hint_subscript!(PyDict::TYPE_HINT, K::OUTPUT_TYPE, V::OUTPUT_TYPE);
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let dict = PyDict::new(py);
for (k, v) in self {
dict.set_item(k, v)?;
}
Ok(dict)
}
}
impl<'a, 'py, K, V, H> IntoPyObject<'py> for &'a indexmap::IndexMap<K, V, H>
where
&'a K: IntoPyObject<'py> + Eq + hash::Hash,
&'a V: IntoPyObject<'py>,
H: hash::BuildHasher,
{
type Target = PyDict;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr =
type_hint_subscript!(PyDict::TYPE_HINT, <&K>::OUTPUT_TYPE, <&V>::OUTPUT_TYPE);
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let dict = PyDict::new(py);
for (k, v) in self {
dict.set_item(k, v)?;
}
Ok(dict)
}
}
impl<'py, K, V, S> FromPyObject<'_, 'py> for indexmap::IndexMap<K, V, S>
where
K: FromPyObjectOwned<'py> + Eq + hash::Hash,
V: FromPyObjectOwned<'py>,
S: hash::BuildHasher + Default,
{
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr =
type_hint_subscript!(PyDict::TYPE_HINT, K::INPUT_TYPE, V::INPUT_TYPE);
fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {
let dict = ob.cast::<PyDict>()?;
let mut ret = indexmap::IndexMap::with_capacity_and_hasher(dict.len(), S::default());
for (k, v) in dict.iter() {
ret.insert(
k.extract().map_err(Into::into)?,
v.extract().map_err(Into::into)?,
);
}
Ok(ret)
}
}
#[cfg(test)]
mod test_indexmap {
use crate::types::*;
use crate::{IntoPyObject, Python};
#[test]
fn test_indexmap_indexmap_into_pyobject() {
Python::attach(|py| {
let mut map = indexmap::IndexMap::<i32, i32>::new();
map.insert(1, 1);
let py_map = (&map).into_pyobject(py).unwrap();
assert_eq!(py_map.len(), 1);
assert!(
py_map
.get_item(1)
.unwrap()
.unwrap()
.extract::<i32>()
.unwrap()
== 1
);
assert_eq!(
map,
py_map.extract::<indexmap::IndexMap::<i32, i32>>().unwrap()
);
});
}
#[test]
fn test_indexmap_indexmap_into_dict() {
Python::attach(|py| {
let mut map = indexmap::IndexMap::<i32, i32>::new();
map.insert(1, 1);
let py_map = map.into_py_dict(py).unwrap();
assert_eq!(py_map.len(), 1);
assert_eq!(
py_map
.get_item(1)
.unwrap()
.unwrap()
.extract::<i32>()
.unwrap(),
1
);
});
}
#[test]
fn test_indexmap_indexmap_insertion_order_round_trip() {
Python::attach(|py| {
let n = 20;
let mut map = indexmap::IndexMap::<i32, i32>::new();
for i in 1..=n {
if i % 2 == 1 {
map.insert(i, i);
} else {
map.insert(n - i, i);
}
}
let py_map = (&map).into_py_dict(py).unwrap();
let trip_map = py_map.extract::<indexmap::IndexMap<i32, i32>>().unwrap();
for (((k1, v1), (k2, v2)), (k3, v3)) in
map.iter().zip(py_map.iter()).zip(trip_map.iter())
{
let k2 = k2.extract::<i32>().unwrap();
let v2 = v2.extract::<i32>().unwrap();
assert_eq!((k1, v1), (&k2, &v2));
assert_eq!((k1, v1), (k3, v3));
assert_eq!((&k2, &v2), (k3, v3));
}
});
}
}
File diff suppressed because it is too large. Load diff
+22
View File
@@ -0,0 +1,22 @@
//! This module contains conversions between various Rust object and their representation in Python.
pub mod anyhow;
pub mod bigdecimal;
pub mod bytes;
pub mod chrono;
pub mod chrono_tz;
pub mod either;
pub mod eyre;
pub mod hashbrown;
pub mod indexmap;
pub mod jiff;
pub mod num_bigint;
pub mod num_complex;
pub mod num_rational;
pub mod ordered_float;
pub mod rust_decimal;
pub mod serde;
pub mod smallvec;
pub(crate) mod std;
pub mod time;
pub mod uuid;
+476
View File
@@ -0,0 +1,476 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
#![cfg(feature = "num-bigint")]
//! Conversions to and from [num-bigint](https://docs.rs/num-bigint)’s [`BigInt`] and [`BigUint`] types.
//!
//! This is useful for converting Python integers when they may not fit in Rust's built-in integer types.
//!
//! # Setup
//!
//! To use this feature, add this to your **`Cargo.toml`**:
//!
//! ```toml
//! [dependencies]
//! num-bigint = "*"
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"num-bigint\"] }")]
//! ```
//!
//! Note that you must use compatible versions of num-bigint and PyO3.
//! The required num-bigint version may vary based on the version of PyO3.
//!
//! ## Examples
//!
//! Using [`BigInt`] to correctly increment an arbitrary precision integer.
//! This is not possible with Rust's native integers if the Python integer is too large,
//! in which case it will fail its conversion and raise `OverflowError`.
//! ```rust,no_run
//! use num_bigint::BigInt;
//! use pyo3::prelude::*;
//!
//! #[pyfunction]
//! fn add_one(n: BigInt) -> BigInt {
//! n + 1
//! }
//!
//! #[pymodule]
//! fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
//! m.add_function(wrap_pyfunction!(add_one, m)?)?;
//! Ok(())
//! }
//! ```
//!
//! Python code:
//! ```python
//! from my_module import add_one
//!
//! n = 1 << 1337
//! value = add_one(n)
//!
//! assert n + 1 == value
//! ```
#[cfg(Py_LIMITED_API)]
use crate::types::{bytes::PyBytesMethods, PyBytes};
use crate::{
conversion::IntoPyObject, std::num::nb_index, types::PyInt, Borrowed, Bound, FromPyObject,
PyAny, PyErr, PyResult, Python,
};
use num_bigint::{BigInt, BigUint};
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
#[cfg(feature = "experimental-inspect")]
use crate::PyTypeInfo;
#[cfg(not(Py_LIMITED_API))]
use num_bigint::Sign;
// for identical functionality between BigInt and BigUint
macro_rules! bigint_conversion {
($rust_ty: ty, $is_signed: literal) => {
#[cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))]
impl<'py> IntoPyObject<'py> for $rust_ty {
type Target = PyInt;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&$rust_ty>::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(&self).into_pyobject(py)
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))]
impl<'py> IntoPyObject<'py> for &$rust_ty {
type Target = PyInt;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
use num_traits::ToBytes;
#[cfg(all(not(Py_LIMITED_API), Py_3_13))]
{
use crate::conversions::std::num::int_from_ne_bytes;
let bytes = self.to_ne_bytes();
Ok(int_from_ne_bytes::<{ $is_signed }>(py, &bytes))
}
#[cfg(all(not(Py_LIMITED_API), not(Py_3_13)))]
{
use crate::conversions::std::num::int_from_le_bytes;
let bytes = self.to_le_bytes();
Ok(int_from_le_bytes::<{ $is_signed }>(py, &bytes))
}
#[cfg(Py_LIMITED_API)]
{
use $crate::py_result_ext::PyResultExt;
use $crate::types::any::PyAnyMethods;
let bytes = self.to_le_bytes();
let bytes_obj = PyBytes::new(py, &bytes);
let kwargs = if $is_signed {
let kwargs = crate::types::PyDict::new(py);
kwargs.set_item(crate::intern!(py, "signed"), true)?;
Some(kwargs)
} else {
None
};
unsafe {
py.get_type::<PyInt>()
.call_method("from_bytes", (bytes_obj, "little"), kwargs.as_ref())
.cast_into_unchecked()
}
}
}
}
};
}
bigint_conversion!(BigUint, false);
bigint_conversion!(BigInt, true);
#[cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))]
impl<'py> FromPyObject<'_, 'py> for BigInt {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT;
fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result<BigInt, Self::Error> {
// fast path - checking for subclass of `int` just checks a bit in the type object
let num_owned: Bound<'_, PyInt>;
let num = if let Ok(long) = ob.cast::<PyInt>() {
long
} else {
num_owned = nb_index(&ob)?;
num_owned.as_borrowed()
};
#[cfg(not(Py_LIMITED_API))]
{
let mut buffer = int_to_u32_vec::<true>(&num)?;
let sign = if buffer.last().copied().is_some_and(|last| last >> 31 != 0) {
// BigInt::new takes an unsigned array, so need to convert from two's complement
// flip all bits, 'subtract' 1 (by adding one to the unsigned array)
let mut elements = buffer.iter_mut();
for element in elements.by_ref() {
*element = (!*element).wrapping_add(1);
if *element != 0 {
// if the element didn't wrap over, no need to keep adding further ...
break;
}
}
// ... so just two's complement the rest
for element in elements {
*element = !*element;
}
Sign::Minus
} else {
Sign::Plus
};
Ok(BigInt::new(sign, buffer))
}
#[cfg(Py_LIMITED_API)]
{
let n_bits = int_n_bits(&num)?;
if n_bits == 0 {
return Ok(BigInt::from(0isize));
}
let bytes = int_to_py_bytes(&num, (n_bits + 8) / 8, true)?;
Ok(BigInt::from_signed_bytes_le(bytes.as_bytes()))
}
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))]
impl<'py> FromPyObject<'_, 'py> for BigUint {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = PyInt::TYPE_HINT;
fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result<BigUint, Self::Error> {
// fast path - checking for subclass of `int` just checks a bit in the type object
let num_owned: Bound<'_, PyInt>;
let num = if let Ok(long) = ob.cast::<PyInt>() {
long
} else {
num_owned = nb_index(&ob)?;
num_owned.as_borrowed()
};
#[cfg(not(Py_LIMITED_API))]
{
let buffer = int_to_u32_vec::<false>(&num)?;
Ok(BigUint::new(buffer))
}
#[cfg(Py_LIMITED_API)]
{
let n_bits = int_n_bits(&num)?;
if n_bits == 0 {
return Ok(BigUint::from(0usize));
}
let bytes = int_to_py_bytes(&num, n_bits.div_ceil(8), false)?;
Ok(BigUint::from_bytes_le(bytes.as_bytes()))
}
}
}
#[cfg(not(any(Py_LIMITED_API, Py_3_13)))]
#[inline]
fn int_to_u32_vec<const SIGNED: bool>(long: &Bound<'_, PyInt>) -> PyResult<Vec<u32>> {
use crate::ffi;
let mut buffer = Vec::new();
let n_bits = int_n_bits(long)?;
if n_bits == 0 {
return Ok(buffer);
}
let n_digits = if SIGNED {
(n_bits + 32) / 32
} else {
n_bits.div_ceil(32)
};
buffer.reserve_exact(n_digits);
unsafe {
crate::err::error_on_minusone(
long.py(),
ffi::_PyLong_AsByteArray(
long.as_ptr().cast(),
buffer.as_mut_ptr() as *mut u8,
n_digits * 4,
1,
SIGNED.into(),
),
)?;
buffer.set_len(n_digits)
};
buffer
.iter_mut()
.for_each(|chunk| *chunk = u32::from_le(*chunk));
Ok(buffer)
}
#[cfg(all(not(Py_LIMITED_API), Py_3_13))]
#[inline]
fn int_to_u32_vec<const SIGNED: bool>(long: &Bound<'_, PyInt>) -> PyResult<Vec<u32>> {
use crate::ffi;
let mut buffer = Vec::new();
let mut flags = ffi::Py_ASNATIVEBYTES_LITTLE_ENDIAN;
if !SIGNED {
flags |= ffi::Py_ASNATIVEBYTES_UNSIGNED_BUFFER | ffi::Py_ASNATIVEBYTES_REJECT_NEGATIVE;
}
let n_bytes =
unsafe { ffi::PyLong_AsNativeBytes(long.as_ptr().cast(), core::ptr::null_mut(), 0, flags) };
let n_bytes_unsigned: usize = n_bytes
.try_into()
.map_err(|_| crate::PyErr::fetch(long.py()))?;
if n_bytes == 0 {
return Ok(buffer);
}
let n_digits = n_bytes_unsigned.div_ceil(4);
buffer.reserve_exact(n_digits);
unsafe {
ffi::PyLong_AsNativeBytes(
long.as_ptr().cast(),
buffer.as_mut_ptr().cast(),
(n_digits * 4).try_into().unwrap(),
flags,
);
buffer.set_len(n_digits);
};
buffer
.iter_mut()
.for_each(|chunk| *chunk = u32::from_le(*chunk));
Ok(buffer)
}
#[cfg(Py_LIMITED_API)]
fn int_to_py_bytes<'py>(
long: &Bound<'py, PyInt>,
n_bytes: usize,
is_signed: bool,
) -> PyResult<Bound<'py, PyBytes>> {
use crate::intern;
use crate::types::any::PyAnyMethods;
let py = long.py();
let kwargs = if is_signed {
let kwargs = crate::types::PyDict::new(py);
kwargs.set_item(intern!(py, "signed"), true)?;
Some(kwargs)
} else {
None
};
let bytes = long.call_method(
intern!(py, "to_bytes"),
(n_bytes, intern!(py, "little")),
kwargs.as_ref(),
)?;
Ok(bytes.cast_into()?)
}
#[inline]
#[cfg(any(not(Py_3_13), Py_LIMITED_API))]
fn int_n_bits(long: &Bound<'_, PyInt>) -> PyResult<usize> {
use crate::types::PyAnyMethods;
long.call_method0(crate::intern!(long.py(), "bit_length"))
.and_then(|any| any.extract())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::exceptions::PyTypeError;
use crate::test_utils::generate_unique_module_name;
use crate::types::{PyAnyMethods as _, PyDict, PyModule};
use pyo3_ffi::c_str;
fn rust_fib<T>() -> impl Iterator<Item = T>
where
T: From<u16>,
for<'a> &'a T: core::ops::Add<Output = T>,
{
let mut f0: T = T::from(1);
let mut f1: T = T::from(1);
core::iter::from_fn(move || {
let f2 = &f0 + &f1;
Some(core::mem::replace(&mut f0, core::mem::replace(&mut f1, f2)))
})
}
fn python_fib(py: Python<'_>) -> impl Iterator<Item = Bound<'_, PyAny>> + '_ {
let mut f0 = 1i32.into_pyobject(py).unwrap().into_any();
let mut f1 = 1i32.into_pyobject(py).unwrap().into_any();
core::iter::from_fn(move || {
let f2 = f0.call_method1("__add__", (&f1,)).unwrap();
Some(core::mem::replace(&mut f0, core::mem::replace(&mut f1, f2)))
})
}
#[test]
fn convert_biguint() {
Python::attach(|py| {
// check the first 2000 numbers in the fibonacci sequence
for (py_result, rs_result) in python_fib(py).zip(rust_fib::<BigUint>()).take(2000) {
// Python -> Rust
assert_eq!(py_result.extract::<BigUint>().unwrap(), rs_result);
// Rust -> Python
assert!(py_result.eq(rs_result).unwrap());
}
});
}
#[test]
fn convert_bigint() {
Python::attach(|py| {
// check the first 2000 numbers in the fibonacci sequence
for (py_result, rs_result) in python_fib(py).zip(rust_fib::<BigInt>()).take(2000) {
// Python -> Rust
assert_eq!(py_result.extract::<BigInt>().unwrap(), rs_result);
// Rust -> Python
assert!(py_result.eq(&rs_result).unwrap());
// negate
let rs_result = rs_result * -1;
let py_result = py_result.call_method0("__neg__").unwrap();
// Python -> Rust
assert_eq!(py_result.extract::<BigInt>().unwrap(), rs_result);
// Rust -> Python
assert!(py_result.eq(rs_result).unwrap());
}
});
}
fn python_index_class(py: Python<'_>) -> Bound<'_, PyModule> {
let index_code = c_str!(
r#"
class C:
def __init__(self, x):
self.x = x
def __index__(self):
return self.x
"#
);
PyModule::from_code(
py,
index_code,
c"index.py",
&generate_unique_module_name("index"),
)
.unwrap()
}
#[test]
fn convert_index_class() {
Python::attach(|py| {
let index = python_index_class(py);
let locals = PyDict::new(py);
locals.set_item("index", index).unwrap();
let ob = py.eval(c"index.C(10)", None, Some(&locals)).unwrap();
let _: BigInt = ob.extract().unwrap();
let _: BigUint = ob.extract().unwrap();
});
}
#[test]
fn handle_zero() {
Python::attach(|py| {
let zero: BigInt = 0i32.into_pyobject(py).unwrap().extract().unwrap();
assert_eq!(zero, BigInt::from(0));
})
}
/// `OverflowError` on converting Python int to BigInt, see issue #629
#[test]
fn check_overflow() {
Python::attach(|py| {
macro_rules! test {
($T:ty, $value:expr, $py:expr) => {
let value = $value;
println!("{}: {}", stringify!($T), value);
let python_value = value.clone().into_pyobject(py).unwrap();
let roundtrip_value = python_value.extract::<$T>().unwrap();
assert_eq!(value, roundtrip_value);
};
}
for i in 0..=256usize {
// test a lot of values to help catch other bugs too
test!(BigInt, BigInt::from(i), py);
test!(BigUint, BigUint::from(i), py);
test!(BigInt, -BigInt::from(i), py);
test!(BigInt, BigInt::from(1) << i, py);
test!(BigUint, BigUint::from(1u32) << i, py);
test!(BigInt, -BigInt::from(1) << i, py);
test!(BigInt, (BigInt::from(1) << i) + 1u32, py);
test!(BigUint, (BigUint::from(1u32) << i) + 1u32, py);
test!(BigInt, (-BigInt::from(1) << i) + 1u32, py);
test!(BigInt, (BigInt::from(1) << i) - 1u32, py);
test!(BigUint, (BigUint::from(1u32) << i) - 1u32, py);
test!(BigInt, (-BigInt::from(1) << i) - 1u32, py);
}
});
}
#[test]
fn from_py_float_type_error() {
Python::attach(|py| {
let obj = (12.3f64).into_pyobject(py).unwrap();
let err = obj.extract::<BigInt>().unwrap_err();
assert!(err.is_instance_of::<PyTypeError>(py));
let err = obj.extract::<BigUint>().unwrap_err();
assert!(err.is_instance_of::<PyTypeError>(py));
});
}
}
+365
View File
@@ -0,0 +1,365 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
#![cfg(feature = "num-complex")]
//! Conversions to and from [num-complex](https://docs.rs/num-complex)’
//! [`Complex`]`<`[`f32`]`>` and [`Complex`]`<`[`f64`]`>`.
//!
//! num-complex’ [`Complex`] supports more operations than PyO3's [`PyComplex`]
//! and can be used with the rest of the Rust ecosystem.
//!
//! # Setup
//!
//! To use this feature, add this to your **`Cargo.toml`**:
//!
//! ```toml
//! [dependencies]
//! # change * to the latest versions
//! num-complex = "*"
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"num-complex\"] }")]
//! ```
//!
//! Note that you must use compatible versions of num-complex and PyO3.
//! The required num-complex version may vary based on the version of PyO3.
//!
//! # Examples
//!
//! Using [num-complex](https://docs.rs/num-complex) and [nalgebra](https://docs.rs/nalgebra)
//! to create a pyfunction that calculates the eigenvalues of a 2x2 matrix.
//! ```ignore
//! # // not tested because nalgebra isn't supported on msrv
//! # // please file an issue if it breaks!
//! use nalgebra::base::{dimension::Const, Matrix};
//! use num_complex::Complex;
//! use pyo3::prelude::*;
//!
//! type T = Complex<f64>;
//!
//! #[pyfunction]
//! fn get_eigenvalues(m11: T, m12: T, m21: T, m22: T) -> Vec<T> {
//! let mat = Matrix::<T, Const<2>, Const<2>, _>::new(m11, m12, m21, m22);
//!
//! match mat.eigenvalues() {
//! Some(e) => e.data.as_slice().to_vec(),
//! None => vec![],
//! }
//! }
//!
//! #[pymodule]
//! fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
//! m.add_function(wrap_pyfunction!(get_eigenvalues, m)?)?;
//! Ok(())
//! }
//! # // test
//! # use assert_approx_eq::assert_approx_eq;
//! # use nalgebra::ComplexField;
//! # use pyo3::types::PyComplex;
//! #
//! # fn main() -> PyResult<()> {
//! # Python::attach(|py| -> PyResult<()> {
//! # let module = PyModule::new(py, "my_module")?;
//! #
//! # module.add_function(&wrap_pyfunction!(get_eigenvalues, module)?)?;
//! #
//! # let m11 = PyComplex::from_doubles(py, 0_f64, -1_f64);
//! # let m12 = PyComplex::from_doubles(py, 1_f64, 0_f64);
//! # let m21 = PyComplex::from_doubles(py, 2_f64, -1_f64);
//! # let m22 = PyComplex::from_doubles(py, -1_f64, 0_f64);
//! #
//! # let result = module
//! # .getattr("get_eigenvalues")?
//! # .call1((m11, m12, m21, m22))?;
//! # println!("eigenvalues: {:?}", result);
//! #
//! # let result = result.extract::<Vec<T>>()?;
//! # let e0 = result[0];
//! # let e1 = result[1];
//! #
//! # assert_approx_eq!(e0, Complex::new(1_f64, -1_f64));
//! # assert_approx_eq!(e1, Complex::new(-2_f64, 0_f64));
//! #
//! # Ok(())
//! # })
//! # }
//! ```
//!
//! Python code:
//! ```python
//! from my_module import get_eigenvalues
//!
//! m11 = complex(0,-1)
//! m12 = complex(1,0)
//! m21 = complex(2,-1)
//! m22 = complex(-1,0)
//!
//! result = get_eigenvalues(m11,m12,m21,m22)
//! assert result == [complex(1,-1), complex(-2,0)]
//! ```
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
#[cfg(feature = "experimental-inspect")]
use crate::type_hint_identifier;
use crate::{
ffi, ffi_ptr_ext::FfiPtrExt, types::PyComplex, Borrowed, Bound, FromPyObject, PyAny, PyErr,
Python,
};
use core::ffi::c_double;
use num_complex::Complex;
impl PyComplex {
/// Creates a new Python `PyComplex` object from `num_complex`'s [`Complex`].
pub fn from_complex_bound<F: Into<c_double>>(
py: Python<'_>,
complex: Complex<F>,
) -> Bound<'_, PyComplex> {
unsafe {
ffi::PyComplex_FromDoubles(complex.re.into(), complex.im.into())
.assume_owned(py)
.cast_into_unchecked()
}
}
}
macro_rules! complex_conversion {
($float: ty) => {
#[cfg_attr(docsrs, doc(cfg(feature = "num-complex")))]
impl<'py> crate::conversion::IntoPyObject<'py> for Complex<$float> {
type Target = PyComplex;
type Output = Bound<'py, Self::Target>;
type Error = core::convert::Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("builtins", "complex");
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
unsafe {
Ok(
ffi::PyComplex_FromDoubles(self.re as c_double, self.im as c_double)
.assume_owned(py)
.cast_into_unchecked(),
)
}
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "num-complex")))]
impl<'py> crate::conversion::IntoPyObject<'py> for &Complex<$float> {
type Target = PyComplex;
type Output = Bound<'py, Self::Target>;
type Error = core::convert::Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <Complex<$float>>::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "num-complex")))]
impl FromPyObject<'_, '_> for Complex<$float> {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = type_hint_identifier!("builtins", "complex");
fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Complex<$float>, Self::Error> {
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
unsafe {
let val = ffi::PyComplex_AsCComplex(obj.as_ptr());
if val.real == -1.0 {
if let Some(err) = PyErr::take(obj.py()) {
return Err(err);
}
}
Ok(Complex::new(val.real as $float, val.imag as $float))
}
#[cfg(any(Py_LIMITED_API, PyPy))]
unsafe {
use $crate::types::any::PyAnyMethods;
let complex;
let obj = if obj.is_instance_of::<PyComplex>() {
obj
} else if let Some(method) =
obj.lookup_special(crate::intern!(obj.py(), "__complex__"))?
{
complex = method.call0()?;
complex.as_borrowed()
} else {
// `obj` might still implement `__float__` or `__index__`, which will be
// handled by `PyComplex_{Real,Imag}AsDouble`, including propagating any
// errors if those methods don't exist / raise exceptions.
obj
};
let ptr = obj.as_ptr();
let real = ffi::PyComplex_RealAsDouble(ptr);
if real == -1.0 {
if let Some(err) = PyErr::take(obj.py()) {
return Err(err);
}
}
let imag = ffi::PyComplex_ImagAsDouble(ptr);
Ok(Complex::new(real as $float, imag as $float))
}
}
}
};
}
complex_conversion!(f32);
complex_conversion!(f64);
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::generate_unique_module_name;
use crate::types::PyAnyMethods as _;
use crate::types::{complex::PyComplexMethods, PyModule};
use crate::IntoPyObject;
#[test]
fn from_complex() {
Python::attach(|py| {
let complex = Complex::new(3.0, 1.2);
let py_c = PyComplex::from_complex_bound(py, complex);
assert_eq!(py_c.real(), 3.0);
assert_eq!(py_c.imag(), 1.2);
});
}
#[test]
fn to_from_complex() {
Python::attach(|py| {
let val = Complex::new(3.0f64, 1.2);
let obj = val.into_pyobject(py).unwrap();
assert_eq!(obj.extract::<Complex<f64>>().unwrap(), val);
});
}
#[test]
fn from_complex_err() {
Python::attach(|py| {
let obj = vec![1i32].into_pyobject(py).unwrap();
assert!(obj.extract::<Complex<f64>>().is_err());
});
}
#[test]
fn from_python_magic() {
Python::attach(|py| {
let module = PyModule::from_code(
py,
cr#"
class A:
def __complex__(self): return 3.0+1.2j
class B:
def __float__(self): return 3.0
class C:
def __index__(self): return 3
"#,
c"test.py",
&generate_unique_module_name("test"),
)
.unwrap();
let from_complex = module.getattr("A").unwrap().call0().unwrap();
assert_eq!(
from_complex.extract::<Complex<f64>>().unwrap(),
Complex::new(3.0, 1.2)
);
let from_float = module.getattr("B").unwrap().call0().unwrap();
assert_eq!(
from_float.extract::<Complex<f64>>().unwrap(),
Complex::new(3.0, 0.0)
);
let from_index = module.getattr("C").unwrap().call0().unwrap();
assert_eq!(
from_index.extract::<Complex<f64>>().unwrap(),
Complex::new(3.0, 0.0)
);
})
}
#[test]
fn from_python_inherited_magic() {
Python::attach(|py| {
let module = PyModule::from_code(
py,
cr#"
class First: pass
class ComplexMixin:
def __complex__(self): return 3.0+1.2j
class FloatMixin:
def __float__(self): return 3.0
class IndexMixin:
def __index__(self): return 3
class A(First, ComplexMixin): pass
class B(First, FloatMixin): pass
class C(First, IndexMixin): pass
"#,
c"test.py",
&generate_unique_module_name("test"),
)
.unwrap();
let from_complex = module.getattr("A").unwrap().call0().unwrap();
assert_eq!(
from_complex.extract::<Complex<f64>>().unwrap(),
Complex::new(3.0, 1.2)
);
let from_float = module.getattr("B").unwrap().call0().unwrap();
assert_eq!(
from_float.extract::<Complex<f64>>().unwrap(),
Complex::new(3.0, 0.0)
);
let from_index = module.getattr("C").unwrap().call0().unwrap();
assert_eq!(
from_index.extract::<Complex<f64>>().unwrap(),
Complex::new(3.0, 0.0)
);
})
}
#[test]
fn from_python_noncallable_descriptor_magic() {
// Functions and lambdas implement the descriptor protocol in a way that makes
// `type(inst).attr(inst)` equivalent to `inst.attr()` for methods, but this isn't the only
// way the descriptor protocol might be implemented.
Python::attach(|py| {
let module = PyModule::from_code(
py,
cr#"
class A:
@property
def __complex__(self):
return lambda: 3.0+1.2j
"#,
c"test.py",
&generate_unique_module_name("test"),
)
.unwrap();
let obj = module.getattr("A").unwrap().call0().unwrap();
assert_eq!(
obj.extract::<Complex<f64>>().unwrap(),
Complex::new(3.0, 1.2)
);
})
}
#[test]
fn from_python_nondescriptor_magic() {
// Magic methods don't need to implement the descriptor protocol, if they're callable.
Python::attach(|py| {
let module = PyModule::from_code(
py,
cr#"
class MyComplex:
def __call__(self): return 3.0+1.2j
class A:
__complex__ = MyComplex()
"#,
c"test.py",
&generate_unique_module_name("test"),
)
.unwrap();
let obj = module.getattr("A").unwrap().call0().unwrap();
assert_eq!(
obj.extract::<Complex<f64>>().unwrap(),
Complex::new(3.0, 1.2)
);
})
}
}
+280
View File
@@ -0,0 +1,280 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
#![cfg(feature = "num-rational")]
//! Conversions to and from [num-rational](https://docs.rs/num-rational) types.
//!
//! This is useful for converting between Python's [fractions.Fraction](https://docs.python.org/3/library/fractions.html) into and from a native Rust
//! type.
//!
//!
//! To use this feature, add to your **`Cargo.toml`**:
//!
//! ```toml
//! [dependencies]
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"num-rational\"] }")]
//! num-rational = "0.4.1"
//! ```
//!
//! # Example
//!
//! Rust code to create a function that adds five to a fraction:
//!
//! ```rust,no_run
//! use num_rational::Ratio;
//! use pyo3::prelude::*;
//!
//! #[pyfunction]
//! fn add_five_to_fraction(fraction: Ratio<i32>) -> Ratio<i32> {
//! fraction + Ratio::new(5, 1)
//! }
//!
//! #[pymodule]
//! fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
//! m.add_function(wrap_pyfunction!(add_five_to_fraction, m)?)?;
//! Ok(())
//! }
//! ```
//!
//! Python code that validates the functionality:
//! ```python
//! from my_module import add_five_to_fraction
//! from fractions import Fraction
//!
//! fraction = Fraction(2,1)
//! fraction_plus_five = add_five_to_fraction(f)
//! assert fraction + 5 == fraction_plus_five
//! ```
use crate::conversion::IntoPyObject;
use crate::ffi;
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
use crate::sync::PyOnceLock;
#[cfg(feature = "experimental-inspect")]
use crate::type_hint_identifier;
use crate::types::any::PyAnyMethods;
use crate::types::PyType;
use crate::{Borrowed, Bound, FromPyObject, Py, PyAny, PyErr, PyResult, Python};
#[cfg(feature = "num-bigint")]
use num_bigint::BigInt;
use num_rational::Ratio;
static FRACTION_CLS: PyOnceLock<Py<PyType>> = PyOnceLock::new();
fn get_fraction_cls(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
FRACTION_CLS.import(py, "fractions", "Fraction")
}
macro_rules! rational_conversion {
($int: ty) => {
impl<'py> FromPyObject<'_, 'py> for Ratio<$int> {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = type_hint_identifier!("fractions", "Fraction");
fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {
let py = obj.py();
let py_numerator_obj = obj.getattr(crate::intern!(py, "numerator"))?;
let py_denominator_obj = obj.getattr(crate::intern!(py, "denominator"))?;
let numerator_owned = unsafe {
Bound::from_owned_ptr_or_err(py, ffi::PyNumber_Long(py_numerator_obj.as_ptr()))?
};
let denominator_owned = unsafe {
Bound::from_owned_ptr_or_err(
py,
ffi::PyNumber_Long(py_denominator_obj.as_ptr()),
)?
};
let rs_numerator: $int = numerator_owned.extract()?;
let rs_denominator: $int = denominator_owned.extract()?;
Ok(Ratio::new(rs_numerator, rs_denominator))
}
}
impl<'py> IntoPyObject<'py> for Ratio<$int> {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&Ratio<$int>>::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(&self).into_pyobject(py)
}
}
impl<'py> IntoPyObject<'py> for &Ratio<$int> {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("fractions", "Fraction");
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
get_fraction_cls(py)?.call1((self.numer().clone(), self.denom().clone()))
}
}
};
}
rational_conversion!(i8);
rational_conversion!(i16);
rational_conversion!(i32);
rational_conversion!(isize);
rational_conversion!(i64);
#[cfg(feature = "num-bigint")]
rational_conversion!(BigInt);
#[cfg(test)]
mod tests {
use super::*;
use crate::types::dict::PyDictMethods;
use crate::types::PyDict;
#[cfg(not(target_arch = "wasm32"))]
use proptest::prelude::*;
#[test]
fn test_negative_fraction() {
Python::attach(|py| {
let locals = PyDict::new(py);
py.run(
c"import fractions\npy_frac = fractions.Fraction(-0.125)",
None,
Some(&locals),
)
.unwrap();
let py_frac = locals.get_item("py_frac").unwrap().unwrap();
let roundtripped: Ratio<i32> = py_frac.extract().unwrap();
let rs_frac = Ratio::new(-1, 8);
assert_eq!(roundtripped, rs_frac);
})
}
#[test]
fn test_obj_with_incorrect_atts() {
Python::attach(|py| {
let locals = PyDict::new(py);
py.run(
c"not_fraction = \"contains_incorrect_atts\"",
None,
Some(&locals),
)
.unwrap();
let py_frac = locals.get_item("not_fraction").unwrap().unwrap();
assert!(py_frac.extract::<Ratio<i32>>().is_err());
})
}
#[test]
fn test_fraction_with_fraction_type() {
Python::attach(|py| {
let locals = PyDict::new(py);
py.run(
c"import fractions\npy_frac = fractions.Fraction(fractions.Fraction(10))",
None,
Some(&locals),
)
.unwrap();
let py_frac = locals.get_item("py_frac").unwrap().unwrap();
let roundtripped: Ratio<i32> = py_frac.extract().unwrap();
let rs_frac = Ratio::new(10, 1);
assert_eq!(roundtripped, rs_frac);
})
}
#[test]
fn test_fraction_with_decimal() {
Python::attach(|py| {
let locals = PyDict::new(py);
py.run(
c"import fractions\n\nfrom decimal import Decimal\npy_frac = fractions.Fraction(Decimal(\"1.1\"))",
None,
Some(&locals),
)
.unwrap();
let py_frac = locals.get_item("py_frac").unwrap().unwrap();
let roundtripped: Ratio<i32> = py_frac.extract().unwrap();
let rs_frac = Ratio::new(11, 10);
assert_eq!(roundtripped, rs_frac);
})
}
#[test]
fn test_fraction_with_num_den() {
Python::attach(|py| {
let locals = PyDict::new(py);
py.run(
c"import fractions\npy_frac = fractions.Fraction(10,5)",
None,
Some(&locals),
)
.unwrap();
let py_frac = locals.get_item("py_frac").unwrap().unwrap();
let roundtripped: Ratio<i32> = py_frac.extract().unwrap();
let rs_frac = Ratio::new(10, 5);
assert_eq!(roundtripped, rs_frac);
})
}
#[cfg(target_arch = "wasm32")]
#[test]
fn test_int_roundtrip() {
Python::attach(|py| {
let rs_frac = Ratio::new(1i32, 2);
let py_frac = rs_frac.into_pyobject(py).unwrap();
let roundtripped: Ratio<i32> = py_frac.extract().unwrap();
assert_eq!(rs_frac, roundtripped);
// float conversion
})
}
#[cfg(target_arch = "wasm32")]
#[test]
fn test_big_int_roundtrip() {
Python::attach(|py| {
let rs_frac = Ratio::from_float(5.5).unwrap();
let py_frac = rs_frac.clone().into_pyobject(py).unwrap();
let roundtripped: Ratio<BigInt> = py_frac.extract().unwrap();
assert_eq!(rs_frac, roundtripped);
})
}
#[cfg(not(target_arch = "wasm32"))]
proptest! {
#[test]
fn test_int_roundtrip(num in any::<i32>(), den in any::<i32>()) {
Python::attach(|py| {
let rs_frac = Ratio::new(num, den);
let py_frac = rs_frac.into_pyobject(py).unwrap();
let roundtripped: Ratio<i32> = py_frac.extract().unwrap();
assert_eq!(rs_frac, roundtripped);
})
}
#[test]
#[cfg(feature = "num-bigint")]
fn test_big_int_roundtrip(num in any::<f32>()) {
Python::attach(|py| {
let rs_frac = Ratio::from_float(num).unwrap();
let py_frac = rs_frac.clone().into_pyobject(py).unwrap();
let roundtripped: Ratio<BigInt> = py_frac.extract().unwrap();
assert_eq!(roundtripped, rs_frac);
})
}
}
#[test]
fn test_infinity() {
Python::attach(|py| {
let locals = PyDict::new(py);
let py_bound = py.run(
c"import fractions\npy_frac = fractions.Fraction(\"Infinity\")",
None,
Some(&locals),
);
assert!(py_bound.is_err());
})
}
}
+345
View File
@@ -0,0 +1,345 @@
#![cfg(feature = "ordered-float")]
//! Conversions to and from [ordered-float](https://docs.rs/ordered-float) types.
//! [`NotNan`]`<`[`f32`]`>` and [`NotNan`]`<`[`f64`]`>`.
//! [`OrderedFloat`]`<`[`f32`]`>` and [`OrderedFloat`]`<`[`f64`]`>`.
//!
//! This is useful for converting between Python's float into and from a native Rust type.
//!
//! Take care when comparing sorted collections of float types between Python and Rust.
//! They will likely differ due to the ambiguous sort order of NaNs in Python.
//
//!
//! To use this feature, add to your **`Cargo.toml`**:
//!
//! ```toml
//! [dependencies]
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"ordered-float\"] }")]
//! ordered-float = "5.0.0"
//! ```
//!
//! # Example
//!
//! Rust code to create functions that add ordered floats:
//!
//! ```rust,no_run
//! use ordered_float::{NotNan, OrderedFloat};
//! use pyo3::prelude::*;
//!
//! #[pyfunction]
//! fn add_not_nans(a: NotNan<f64>, b: NotNan<f64>) -> NotNan<f64> {
//! a + b
//! }
//!
//! #[pyfunction]
//! fn add_ordered_floats(a: OrderedFloat<f64>, b: OrderedFloat<f64>) -> OrderedFloat<f64> {
//! a + b
//! }
//!
//! #[pymodule]
//! fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
//! m.add_function(wrap_pyfunction!(add_not_nans, m)?)?;
//! m.add_function(wrap_pyfunction!(add_ordered_floats, m)?)?;
//! Ok(())
//! }
//! ```
//!
//! Python code that validates the functionality:
//! ```python
//! from my_module import add_not_nans, add_ordered_floats
//!
//! assert add_not_nans(1.0,2.0) == 3.0
//! assert add_ordered_floats(1.0,2.0) == 3.0
//! ```
use crate::conversion::IntoPyObject;
use crate::exceptions::PyValueError;
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
use crate::types::PyFloat;
use crate::{Borrowed, Bound, FromPyObject, PyAny, Python};
use core::convert::Infallible;
use ordered_float::{NotNan, OrderedFloat};
macro_rules! float_conversions {
($wrapper:ident, $float_type:ty, $constructor:expr) => {
impl<'a, 'py> FromPyObject<'a, 'py> for $wrapper<$float_type> {
type Error = <$float_type as FromPyObject<'a, 'py>>::Error;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = <$float_type>::INPUT_TYPE;
fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
let val: $float_type = obj.extract()?;
$constructor(val)
}
}
impl<'py> IntoPyObject<'py> for $wrapper<$float_type> {
type Target = PyFloat;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <$float_type>::OUTPUT_TYPE;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
self.into_inner().into_pyobject(py)
}
}
impl<'py> IntoPyObject<'py> for &$wrapper<$float_type> {
type Target = PyFloat;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <$wrapper<$float_type>>::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
};
}
float_conversions!(OrderedFloat, f32, |val| Ok(OrderedFloat(val)));
float_conversions!(OrderedFloat, f64, |val| Ok(OrderedFloat(val)));
float_conversions!(NotNan, f32, |val| NotNan::new(val)
.map_err(|e| PyValueError::new_err(e.to_string())));
float_conversions!(NotNan, f64, |val| NotNan::new(val)
.map_err(|e| PyValueError::new_err(e.to_string())));
#[cfg(test)]
mod test_ordered_float {
use super::*;
use crate::types::dict::IntoPyDict;
use crate::types::PyAnyMethods;
use alloc::ffi::CString;
use core::ffi::CStr;
#[cfg(not(target_arch = "wasm32"))]
use proptest::prelude::*;
fn py_run<'py>(py: Python<'py>, script: &CStr, locals: impl IntoPyDict<'py>) {
py.run(script, None, Some(&locals.into_py_dict(py).unwrap()))
.unwrap()
}
macro_rules! float_roundtrip_tests {
($wrapper:ident, $float_type:ty, $constructor:expr, $standard_test:ident, $wasm_test:ident, $infinity_test:ident, $zero_test:ident) => {
#[cfg(not(target_arch = "wasm32"))]
proptest! {
#[test]
fn $standard_test(inner_f: $float_type) {
let f = $constructor(inner_f);
Python::attach(|py| {
let f_py: Bound<'_, PyFloat> = f.into_pyobject(py).unwrap();
py_run(py, &CString::new(format!(
"import math\nassert math.isclose(f_py, {})",
inner_f as f64 // Always interpret the literal rs float value as f64
// so that it's comparable with the python float
)).unwrap(), [("f_py", &f_py)]);
let roundtripped_f: $wrapper<$float_type> = f_py.extract().unwrap();
assert_eq!(f, roundtripped_f);
})
}
}
#[cfg(target_arch = "wasm32")]
#[test]
fn $wasm_test() {
let inner_f = 10.0;
let f = $constructor(inner_f);
Python::attach(|py| {
let f_py: Bound<'_, PyFloat> = f.into_pyobject(py).unwrap();
py_run(
py,
&CString::new(format!(
"import math\nassert math.isclose(f_py, {})",
inner_f as f64 // Always interpret the literal rs float value as f64
// so that it's comparable with the python float
))
.unwrap(),
[("f_py", &f_py)],
);
let roundtripped_f: $wrapper<$float_type> = f_py.extract().unwrap();
assert_eq!(f, roundtripped_f);
})
}
#[test]
fn $infinity_test() {
let inner_pinf = <$float_type>::INFINITY;
let pinf = $constructor(inner_pinf);
let inner_ninf = <$float_type>::NEG_INFINITY;
let ninf = $constructor(inner_ninf);
Python::attach(|py| {
let pinf_py: Bound<'_, PyFloat> = pinf.into_pyobject(py).unwrap();
let ninf_py: Bound<'_, PyFloat> = ninf.into_pyobject(py).unwrap();
py_run(
py,
c"\
assert pinf_py == float('inf')\n\
assert ninf_py == float('-inf')",
[("pinf_py", &pinf_py), ("ninf_py", &ninf_py)],
);
let roundtripped_pinf: $wrapper<$float_type> = pinf_py.extract().unwrap();
let roundtripped_ninf: $wrapper<$float_type> = ninf_py.extract().unwrap();
assert_eq!(pinf, roundtripped_pinf);
assert_eq!(ninf, roundtripped_ninf);
})
}
#[test]
fn $zero_test() {
let inner_pzero: $float_type = 0.0;
let pzero = $constructor(inner_pzero);
let inner_nzero: $float_type = -0.0;
let nzero = $constructor(inner_nzero);
Python::attach(|py| {
let pzero_py: Bound<'_, PyFloat> = pzero.into_pyobject(py).unwrap();
let nzero_py: Bound<'_, PyFloat> = nzero.into_pyobject(py).unwrap();
// This python script verifies that the values are 0.0 in magnitude
// and that the signs are correct(+0.0 vs -0.0)
py_run(
py,
c"\
import math\n\
assert pzero_py == 0.0\n\
assert math.copysign(1.0, pzero_py) > 0.0\n\
assert nzero_py == 0.0\n\
assert math.copysign(1.0, nzero_py) < 0.0",
[("pzero_py", &pzero_py), ("nzero_py", &nzero_py)],
);
let roundtripped_pzero: $wrapper<$float_type> = pzero_py.extract().unwrap();
let roundtripped_nzero: $wrapper<$float_type> = nzero_py.extract().unwrap();
assert_eq!(pzero, roundtripped_pzero);
assert_eq!(roundtripped_pzero.signum(), 1.0);
assert_eq!(nzero, roundtripped_nzero);
assert_eq!(roundtripped_nzero.signum(), -1.0);
})
}
};
}
float_roundtrip_tests!(
OrderedFloat,
f32,
OrderedFloat,
ordered_float_f32_standard,
ordered_float_f32_wasm,
ordered_float_f32_infinity,
ordered_float_f32_zero
);
float_roundtrip_tests!(
OrderedFloat,
f64,
OrderedFloat,
ordered_float_f64_standard,
ordered_float_f64_wasm,
ordered_float_f64_infinity,
ordered_float_f64_zero
);
float_roundtrip_tests!(
NotNan,
f32,
|val| NotNan::new(val).unwrap(),
not_nan_f32_standard,
not_nan_f32_wasm,
not_nan_f32_infinity,
not_nan_f32_zero
);
float_roundtrip_tests!(
NotNan,
f64,
|val| NotNan::new(val).unwrap(),
not_nan_f64_standard,
not_nan_f64_wasm,
not_nan_f64_infinity,
not_nan_f64_zero
);
macro_rules! ordered_float_pynan_tests {
($test_name:ident, $float_type:ty) => {
#[test]
fn $test_name() {
let inner_nan: $float_type = <$float_type>::NAN;
let nan = OrderedFloat(inner_nan);
Python::attach(|py| {
let nan_py: Bound<'_, PyFloat> = nan.into_pyobject(py).unwrap();
py_run(
py,
c"import math\nassert math.isnan(nan_py)",
[("nan_py", &nan_py)],
);
let roundtripped_nan: OrderedFloat<$float_type> = nan_py.extract().unwrap();
assert_eq!(nan, roundtripped_nan);
})
}
};
}
ordered_float_pynan_tests!(test_ordered_float_pynan_f32, f32);
ordered_float_pynan_tests!(test_ordered_float_pynan_f64, f64);
macro_rules! not_nan_pynan_tests {
($test_name:ident, $float_type:ty) => {
#[test]
fn $test_name() {
Python::attach(|py| {
let nan_py = py.eval(c"float('nan')", None, None).unwrap();
let nan_rs: Result<NotNan<$float_type>, _> = nan_py.extract();
assert!(nan_rs.is_err());
})
}
};
}
not_nan_pynan_tests!(test_not_nan_pynan_f32, f32);
not_nan_pynan_tests!(test_not_nan_pynan_f64, f64);
macro_rules! py64_rs32 {
($test_name:ident, $wrapper:ident, $float_type:ty) => {
#[test]
fn $test_name() {
Python::attach(|py| {
let py_64 = py
.import("sys")
.unwrap()
.getattr("float_info")
.unwrap()
.getattr("max")
.unwrap();
let rs_32 = py_64.extract::<$wrapper<f32>>().unwrap();
// The python f64 is not representable in a rust f32
assert!(rs_32.is_infinite());
})
}
};
}
py64_rs32!(ordered_float_f32, OrderedFloat, f32);
py64_rs32!(ordered_float_f64, OrderedFloat, f64);
py64_rs32!(not_nan_f32, NotNan, f32);
py64_rs32!(not_nan_f64, NotNan, f64);
}
+252
View File
@@ -0,0 +1,252 @@
#![cfg(feature = "rust_decimal")]
//! Conversions to and from [rust_decimal](https://docs.rs/rust_decimal)'s [`Decimal`] type.
//!
//! This is useful for converting Python's decimal.Decimal into and from a native Rust type.
//!
//! # Setup
//!
//! To use this feature, add to your **`Cargo.toml`**:
//!
//! ```toml
//! [dependencies]
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"rust_decimal\"] }")]
//! rust_decimal = "1.0"
//! ```
//!
//! Note that you must use a compatible version of rust_decimal and PyO3.
//! The required rust_decimal version may vary based on the version of PyO3.
//!
//! # Example
//!
//! Rust code to create a function that adds one to a Decimal
//!
//! ```rust,no_run
//! use rust_decimal::Decimal;
//! use pyo3::prelude::*;
//!
//! #[pyfunction]
//! fn add_one(d: Decimal) -> Decimal {
//! d + Decimal::ONE
//! }
//!
//! #[pymodule]
//! fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
//! m.add_function(wrap_pyfunction!(add_one, m)?)?;
//! Ok(())
//! }
//! ```
//!
//! Python code that validates the functionality
//!
//!
//! ```python
//! from my_module import add_one
//! from decimal import Decimal
//!
//! d = Decimal("2")
//! value = add_one(d)
//!
//! assert d + 1 == value
//! ```
use crate::conversion::IntoPyObject;
use crate::exceptions::PyValueError;
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
use crate::sync::PyOnceLock;
#[cfg(feature = "experimental-inspect")]
use crate::type_hint_identifier;
use crate::types::any::PyAnyMethods;
use crate::types::string::PyStringMethods;
use crate::types::PyType;
use crate::{Borrowed, Bound, FromPyObject, Py, PyAny, PyErr, PyResult, Python};
use core::str::FromStr;
use rust_decimal::Decimal;
impl FromPyObject<'_, '_> for Decimal {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = type_hint_identifier!("decimal", "Decimal");
fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
// use the string representation to not be lossy
if let Ok(val) = obj.extract() {
Ok(Decimal::new(val, 0))
} else {
let py_str = &obj.str()?;
let rs_str = &py_str.to_cow()?;
Decimal::from_str(rs_str).or_else(|_| {
Decimal::from_scientific(rs_str).map_err(|e| PyValueError::new_err(e.to_string()))
})
}
}
}
static DECIMAL_CLS: PyOnceLock<Py<PyType>> = PyOnceLock::new();
fn get_decimal_cls(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
DECIMAL_CLS.import(py, "decimal", "Decimal")
}
impl<'py> IntoPyObject<'py> for Decimal {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("decimal", "Decimal");
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let dec_cls = get_decimal_cls(py)?;
// now call the constructor with the Rust Decimal string-ified
// to not be lossy
dec_cls.call1((self.to_string(),))
}
}
impl<'py> IntoPyObject<'py> for &Decimal {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = Decimal::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
#[cfg(test)]
mod test_rust_decimal {
use super::*;
use crate::types::dict::PyDictMethods;
use crate::types::PyDict;
use alloc::ffi::CString;
#[cfg(not(target_arch = "wasm32"))]
use proptest::prelude::*;
macro_rules! convert_constants {
($name:ident, $rs:expr, $py:literal) => {
#[test]
fn $name() {
Python::attach(|py| {
let rs_orig = $rs;
let rs_dec = rs_orig.into_pyobject(py).unwrap();
let locals = PyDict::new(py);
locals.set_item("rs_dec", &rs_dec).unwrap();
// Checks if Rust Decimal -> Python Decimal conversion is correct
py.run(
&CString::new(format!(
"import decimal\npy_dec = decimal.Decimal({})\nassert py_dec == rs_dec",
$py
))
.unwrap(),
None,
Some(&locals),
)
.unwrap();
// Checks if Python Decimal -> Rust Decimal conversion is correct
let py_dec = locals.get_item("py_dec").unwrap().unwrap();
let py_result: Decimal = py_dec.extract().unwrap();
assert_eq!(rs_orig, py_result);
})
}
};
}
convert_constants!(convert_zero, Decimal::ZERO, "0");
convert_constants!(convert_one, Decimal::ONE, "1");
convert_constants!(convert_neg_one, Decimal::NEGATIVE_ONE, "-1");
convert_constants!(convert_two, Decimal::TWO, "2");
convert_constants!(convert_ten, Decimal::TEN, "10");
convert_constants!(convert_one_hundred, Decimal::ONE_HUNDRED, "100");
convert_constants!(convert_one_thousand, Decimal::ONE_THOUSAND, "1000");
#[cfg(not(target_arch = "wasm32"))]
proptest! {
#[test]
fn test_roundtrip(
lo in any::<u32>(),
mid in any::<u32>(),
high in any::<u32>(),
negative in any::<bool>(),
scale in 0..28u32
) {
let num = Decimal::from_parts(lo, mid, high, negative, scale);
Python::attach(|py| {
let rs_dec = num.into_pyobject(py).unwrap();
let locals = PyDict::new(py);
locals.set_item("rs_dec", &rs_dec).unwrap();
py.run(
&CString::new(format!(
"import decimal\npy_dec = decimal.Decimal(\"{num}\")\nassert py_dec == rs_dec")).unwrap(),
None, Some(&locals)).unwrap();
let roundtripped: Decimal = rs_dec.extract().unwrap();
assert_eq!(num, roundtripped);
})
}
#[test]
fn test_integers(num in any::<i64>()) {
Python::attach(|py| {
let py_num = num.into_pyobject(py).unwrap();
let roundtripped: Decimal = py_num.extract().unwrap();
let rs_dec = Decimal::new(num, 0);
assert_eq!(rs_dec, roundtripped);
})
}
}
#[test]
fn test_nan() {
Python::attach(|py| {
let locals = PyDict::new(py);
py.run(
c"import decimal\npy_dec = decimal.Decimal(\"NaN\")",
None,
Some(&locals),
)
.unwrap();
let py_dec = locals.get_item("py_dec").unwrap().unwrap();
let roundtripped: Result<Decimal, PyErr> = py_dec.extract();
assert!(roundtripped.is_err());
})
}
#[test]
fn test_scientific_notation() {
Python::attach(|py| {
let locals = PyDict::new(py);
py.run(
c"import decimal\npy_dec = decimal.Decimal(\"1e3\")",
None,
Some(&locals),
)
.unwrap();
let py_dec = locals.get_item("py_dec").unwrap().unwrap();
let roundtripped: Decimal = py_dec.extract().unwrap();
let rs_dec = Decimal::from_scientific("1e3").unwrap();
assert_eq!(rs_dec, roundtripped);
})
}
#[test]
fn test_infinity() {
Python::attach(|py| {
let locals = PyDict::new(py);
py.run(
c"import decimal\npy_dec = decimal.Decimal(\"Infinity\")",
None,
Some(&locals),
)
.unwrap();
let py_dec = locals.get_item("py_dec").unwrap().unwrap();
let roundtripped: Result<Decimal, PyErr> = py_dec.extract();
assert!(roundtripped.is_err());
})
}
}
+46
View File
@@ -0,0 +1,46 @@
#![cfg(feature = "serde")]
//! Enables (de)serialization of [`Py`]`<T>` objects via [serde](https://docs.rs/serde).
//!
//! # Setup
//!
//! To use this feature, add this to your **`Cargo.toml`**:
//!
//! ```toml
//! [dependencies]
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"serde\"] }")]
//! serde = "1.0"
//! ```
use crate::{Py, PyAny, PyClass, Python};
use serde::{de, ser, Deserialize, Deserializer, Serialize, Serializer};
impl<T> Serialize for Py<T>
where
T: Serialize + PyClass,
{
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where
S: Serializer,
{
Python::attach(|py| {
self.try_borrow(py)
.map_err(|e| ser::Error::custom(e.to_string()))?
.serialize(serializer)
})
}
}
impl<'de, T> Deserialize<'de> for Py<T>
where
T: PyClass<BaseType = PyAny> + Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Py<T>, D::Error>
where
D: Deserializer<'de>,
{
let deserialized = T::deserialize(deserializer)?;
Python::attach(|py| Py::new(py, deserialized).map_err(|e| de::Error::custom(e.to_string())))
}
}
+160
View File
@@ -0,0 +1,160 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
#![cfg(feature = "smallvec")]
//! Conversions to and from [smallvec](https://docs.rs/smallvec/).
//!
//! # Setup
//!
//! To use this feature, add this to your **`Cargo.toml`**:
//!
//! ```toml
//! [dependencies]
//! # change * to the latest versions
//! smallvec = "*"
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"smallvec\"] }")]
//! ```
//!
//! Note that you must use compatible versions of smallvec and PyO3.
//! The required smallvec version may vary based on the version of PyO3.
use crate::conversion::{FromPyObjectOwned, IntoPyObject};
use crate::exceptions::PyTypeError;
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
#[cfg(feature = "experimental-inspect")]
use crate::type_hint_subscript;
use crate::types::any::PyAnyMethods;
use crate::types::{PySequence, PyString};
use crate::{
err::CastError, ffi, Borrowed, Bound, FromPyObject, PyAny, PyErr, PyResult, PyTypeInfo, Python,
};
use smallvec::{Array, SmallVec};
impl<'py, A> IntoPyObject<'py> for SmallVec<A>
where
A: Array,
A::Item: IntoPyObject<'py>,
{
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = A::Item::SEQUENCE_OUTPUT_TYPE;
/// Turns [`SmallVec<u8>`] into [`PyBytes`], all other `T`s will be turned into a [`PyList`]
///
/// [`PyBytes`]: crate::types::PyBytes
/// [`PyList`]: crate::types::PyList
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
<A::Item>::owned_sequence_into_pyobject(self, py, crate::conversion::private::Token)
}
}
impl<'a, 'py, A> IntoPyObject<'py> for &'a SmallVec<A>
where
A: Array,
&'a A::Item: IntoPyObject<'py>,
{
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&[A::Item]>::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
self.as_slice().into_pyobject(py)
}
}
impl<'py, A> FromPyObject<'_, 'py> for SmallVec<A>
where
A: Array,
A::Item: FromPyObjectOwned<'py>,
{
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr =
type_hint_subscript!(PySequence::TYPE_HINT, A::Item::INPUT_TYPE);
fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {
if obj.is_instance_of::<PyString>() {
return Err(PyTypeError::new_err("Can't extract `str` to `SmallVec`"));
}
extract_sequence(obj)
}
}
fn extract_sequence<'py, A>(obj: Borrowed<'_, 'py, PyAny>) -> PyResult<SmallVec<A>>
where
A: Array,
A::Item: FromPyObjectOwned<'py>,
{
// Types that pass `PySequence_Check` usually implement enough of the sequence protocol
// to support this function and if not, we will only fail extraction safely.
if unsafe { ffi::PySequence_Check(obj.as_ptr()) } == 0 {
return Err(CastError::new(obj, PySequence::type_object(obj.py()).into_any()).into());
}
let mut sv = SmallVec::with_capacity(obj.len().unwrap_or(0));
for item in obj.try_iter()? {
sv.push(item?.extract::<A::Item>().map_err(Into::into)?);
}
Ok(sv)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{PyBytes, PyBytesMethods, PyDict, PyList};
#[test]
fn test_smallvec_from_py_object() {
Python::attach(|py| {
let l = PyList::new(py, [1, 2, 3, 4, 5]).unwrap();
let sv: SmallVec<[u64; 8]> = l.extract().unwrap();
assert_eq!(sv.as_slice(), [1, 2, 3, 4, 5]);
});
}
#[test]
fn test_smallvec_from_py_object_fails() {
Python::attach(|py| {
let dict = PyDict::new(py);
let sv: PyResult<SmallVec<[u64; 8]>> = dict.extract();
assert_eq!(
sv.unwrap_err().to_string(),
"TypeError: 'dict' object is not an instance of 'Sequence'"
);
});
}
#[test]
fn test_smallvec_into_pyobject() {
Python::attach(|py| {
let sv: SmallVec<[u64; 8]> = [1, 2, 3, 4, 5].iter().cloned().collect();
let hso = sv.into_pyobject(py).unwrap();
let l = PyList::new(py, [1, 2, 3, 4, 5]).unwrap();
assert!(l.eq(hso).unwrap());
});
}
#[test]
fn test_smallvec_intopyobject_impl() {
Python::attach(|py| {
let bytes: SmallVec<[u8; 8]> = [1, 2, 3, 4, 5].iter().cloned().collect();
let obj = bytes.clone().into_pyobject(py).unwrap();
assert!(obj.is_instance_of::<PyBytes>());
let obj = obj.cast_into::<PyBytes>().unwrap();
assert_eq!(obj.as_bytes(), &*bytes);
let nums: SmallVec<[u16; 8]> = [1, 2, 3, 4, 5].iter().cloned().collect();
let obj = nums.into_pyobject(py).unwrap();
assert!(obj.is_instance_of::<PyList>());
});
}
}
+318
View File
@@ -0,0 +1,318 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
use crate::conversion::{FromPyObjectOwned, FromPyObjectSequence, IntoPyObject};
#[cfg(feature = "experimental-inspect")]
use crate::inspect::{type_hint_subscript, PyStaticExpr};
use crate::types::any::PyAnyMethods;
use crate::types::PySequence;
use crate::{err::CastError, ffi, FromPyObject, PyAny, PyResult, PyTypeInfo, Python};
use crate::{exceptions, Borrowed, Bound, PyErr};
impl<'py, T, const N: usize> IntoPyObject<'py> for [T; N]
where
T: IntoPyObject<'py>,
{
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = T::SEQUENCE_OUTPUT_TYPE;
/// Turns [`[u8; N]`](core::array) into [`PyBytes`], all other `T`s will be turned into a [`PyList`]
///
/// [`PyBytes`]: crate::types::PyBytes
/// [`PyList`]: crate::types::PyList
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
T::owned_sequence_into_pyobject(self, py, crate::conversion::private::Token)
}
}
impl<'a, 'py, T, const N: usize> IntoPyObject<'py> for &'a [T; N]
where
&'a T: IntoPyObject<'py>,
{
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&[T]>::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
self.as_slice().into_pyobject(py)
}
}
impl<'py, T, const N: usize> FromPyObject<'_, 'py> for [T; N]
where
T: FromPyObjectOwned<'py>,
{
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = type_hint_subscript!(PySequence::TYPE_HINT, T::INPUT_TYPE);
fn extract(obj: Borrowed<'_, 'py, PyAny>) -> PyResult<Self> {
if let Some(extractor) = T::sequence_extractor(obj, crate::conversion::private::Token) {
return extractor.to_array();
}
create_array_from_obj(obj)
}
}
fn create_array_from_obj<'py, T, const N: usize>(obj: Borrowed<'_, 'py, PyAny>) -> PyResult<[T; N]>
where
T: FromPyObjectOwned<'py>,
{
// Types that pass `PySequence_Check` usually implement enough of the sequence protocol
// to support this function and if not, we will only fail extraction safely.
if unsafe { ffi::PySequence_Check(obj.as_ptr()) } == 0 {
return Err(CastError::new(obj, PySequence::type_object(obj.py()).into_any()).into());
}
let seq_len = obj.len()?;
if seq_len != N {
return Err(invalid_sequence_length(N, seq_len));
}
array_try_from_fn(|idx| {
obj.get_item(idx)
.and_then(|any| any.extract().map_err(Into::into))
})
}
// TODO use core::array::try_from_fn, if that stabilises:
// (https://github.com/rust-lang/rust/issues/89379)
fn array_try_from_fn<E, F, T, const N: usize>(mut cb: F) -> Result<[T; N], E>
where
F: FnMut(usize) -> Result<T, E>,
{
// Helper to safely create arrays since the standard library doesn't
// provide one yet. Shouldn't be necessary in the future.
struct ArrayGuard<T, const N: usize> {
dst: *mut T,
initialized: usize,
}
impl<T, const N: usize> Drop for ArrayGuard<T, N> {
fn drop(&mut self) {
debug_assert!(self.initialized <= N);
let initialized_part = core::ptr::slice_from_raw_parts_mut(self.dst, self.initialized);
unsafe {
core::ptr::drop_in_place(initialized_part);
}
}
}
// [MaybeUninit<T>; N] would be "nicer" but is actually difficult to create - there are nightly
// APIs which would make this easier.
let mut array: core::mem::MaybeUninit<[T; N]> = core::mem::MaybeUninit::uninit();
let mut guard: ArrayGuard<T, N> = ArrayGuard {
dst: array.as_mut_ptr() as _,
initialized: 0,
};
unsafe {
let mut value_ptr = array.as_mut_ptr() as *mut T;
for i in 0..N {
core::ptr::write(value_ptr, cb(i)?);
value_ptr = value_ptr.offset(1);
guard.initialized += 1;
}
core::mem::forget(guard);
Ok(array.assume_init())
}
}
pub(crate) fn invalid_sequence_length(expected: usize, actual: usize) -> PyErr {
exceptions::PyValueError::new_err(format!(
"expected a sequence of length {expected} (got {actual})"
))
}
#[cfg(test)]
mod tests {
#[cfg(panic = "unwind")]
use core::sync::atomic::{AtomicUsize, Ordering};
#[cfg(panic = "unwind")]
use std::panic;
use crate::{
conversion::IntoPyObject,
types::{any::PyAnyMethods, PyBytes, PyBytesMethods},
};
use crate::{types::PyList, PyResult, Python};
#[test]
#[cfg(panic = "unwind")]
fn array_try_from_fn() {
static DROP_COUNTER: AtomicUsize = AtomicUsize::new(0);
struct CountDrop;
impl Drop for CountDrop {
fn drop(&mut self) {
DROP_COUNTER.fetch_add(1, Ordering::SeqCst);
}
}
let _ = catch_unwind_silent(move || {
let _: Result<[CountDrop; 4], ()> = super::array_try_from_fn(|idx| {
#[expect(clippy::manual_assert, reason = "testing panic during array creation")]
if idx == 2 {
panic!("peek a boo");
}
Ok(CountDrop)
});
});
assert_eq!(DROP_COUNTER.load(Ordering::SeqCst), 2);
}
#[test]
fn test_extract_bytes_to_array() {
Python::attach(|py| {
let v: [u8; 33] = py
.eval(c"b'abcabcabcabcabcabcabcabcabcabcabc'", None, None)
.unwrap()
.extract()
.unwrap();
assert_eq!(&v, b"abcabcabcabcabcabcabcabcabcabcabc");
})
}
#[test]
fn test_extract_bytes_wrong_length() {
Python::attach(|py| {
let v: PyResult<[u8; 3]> = py.eval(c"b'abcdefg'", None, None).unwrap().extract();
assert_eq!(
v.unwrap_err().to_string(),
"ValueError: expected a sequence of length 3 (got 7)"
);
})
}
#[test]
fn test_extract_bytearray_to_array() {
Python::attach(|py| {
let v: [u8; 33] = py
.eval(
c"bytearray(b'abcabcabcabcabcabcabcabcabcabcabc')",
None,
None,
)
.unwrap()
.extract()
.unwrap();
assert_eq!(&v, b"abcabcabcabcabcabcabcabcabcabcabc");
})
}
#[test]
fn test_extract_small_bytearray_to_array() {
Python::attach(|py| {
let v: [u8; 3] = py
.eval(c"bytearray(b'abc')", None, None)
.unwrap()
.extract()
.unwrap();
assert_eq!(&v, b"abc");
});
}
#[test]
fn test_into_pyobject_array_conversion() {
Python::attach(|py| {
let array: [f32; 4] = [0.0, -16.0, 16.0, 42.0];
let pyobject = array.into_pyobject(py).unwrap();
let pylist = pyobject.cast::<PyList>().unwrap();
assert_eq!(pylist.get_item(0).unwrap().extract::<f32>().unwrap(), 0.0);
assert_eq!(pylist.get_item(1).unwrap().extract::<f32>().unwrap(), -16.0);
assert_eq!(pylist.get_item(2).unwrap().extract::<f32>().unwrap(), 16.0);
assert_eq!(pylist.get_item(3).unwrap().extract::<f32>().unwrap(), 42.0);
});
}
#[test]
fn test_extract_invalid_sequence_length() {
Python::attach(|py| {
let v: PyResult<[u8; 3]> = py
.eval(c"bytearray(b'abcdefg')", None, None)
.unwrap()
.extract();
assert_eq!(
v.unwrap_err().to_string(),
"ValueError: expected a sequence of length 3 (got 7)"
);
})
}
#[test]
fn test_intopyobject_array_conversion() {
Python::attach(|py| {
let array: [f32; 4] = [0.0, -16.0, 16.0, 42.0];
let pylist = array
.into_pyobject(py)
.unwrap()
.cast_into::<PyList>()
.unwrap();
assert_eq!(pylist.get_item(0).unwrap().extract::<f32>().unwrap(), 0.0);
assert_eq!(pylist.get_item(1).unwrap().extract::<f32>().unwrap(), -16.0);
assert_eq!(pylist.get_item(2).unwrap().extract::<f32>().unwrap(), 16.0);
assert_eq!(pylist.get_item(3).unwrap().extract::<f32>().unwrap(), 42.0);
});
}
#[test]
fn test_array_intopyobject_impl() {
Python::attach(|py| {
let bytes: [u8; 6] = *b"foobar";
let obj = bytes.into_pyobject(py).unwrap();
assert!(obj.is_instance_of::<PyBytes>());
let obj = obj.cast_into::<PyBytes>().unwrap();
assert_eq!(obj.as_bytes(), &bytes);
let nums: [u16; 4] = [0, 1, 2, 3];
let obj = nums.into_pyobject(py).unwrap();
assert!(obj.is_instance_of::<PyList>());
});
}
#[test]
fn test_extract_non_iterable_to_array() {
Python::attach(|py| {
let v = py.eval(c"42", None, None).unwrap();
v.extract::<i32>().unwrap();
v.extract::<[i32; 1]>().unwrap_err();
});
}
#[cfg(feature = "macros")]
#[test]
fn test_pyclass_intopy_array_conversion() {
#[crate::pyclass(crate = "crate")]
struct Foo;
Python::attach(|py| {
let array: [Foo; 8] = [Foo, Foo, Foo, Foo, Foo, Foo, Foo, Foo];
let list = array
.into_pyobject(py)
.unwrap()
.cast_into::<PyList>()
.unwrap();
let _bound = list.get_item(4).unwrap().cast::<Foo>().unwrap();
});
}
// https://stackoverflow.com/a/59211505
#[cfg(panic = "unwind")]
fn catch_unwind_silent<F, R>(f: F) -> std::thread::Result<R>
where
F: FnOnce() -> R + panic::UnwindSafe,
{
let prev_hook = panic::take_hook();
panic::set_hook(Box::new(|_| {}));
let result = panic::catch_unwind(f);
panic::set_hook(prev_hook);
result
}
}
+44
View File
@@ -0,0 +1,44 @@
use core::cell::Cell;
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
use crate::{conversion::IntoPyObject, Borrowed, FromPyObject, PyAny, Python};
impl<'py, T: Copy + IntoPyObject<'py>> IntoPyObject<'py> for Cell<T> {
type Target = T::Target;
type Output = T::Output;
type Error = T::Error;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = T::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
self.get().into_pyobject(py)
}
}
impl<'py, T: Copy + IntoPyObject<'py>> IntoPyObject<'py> for &Cell<T> {
type Target = T::Target;
type Output = T::Output;
type Error = T::Error;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = T::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
self.get().into_pyobject(py)
}
}
impl<'a, 'py, T: FromPyObject<'a, 'py>> FromPyObject<'a, 'py> for Cell<T> {
type Error = T::Error;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = T::INPUT_TYPE;
fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
ob.extract().map(Cell::new)
}
}
+220
View File
@@ -0,0 +1,220 @@
use crate::exceptions::PyUnicodeDecodeError;
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
#[cfg(feature = "experimental-inspect")]
use crate::type_object::PyTypeInfo;
use crate::types::PyString;
use crate::{Borrowed, Bound, FromPyObject, IntoPyObject, PyAny, PyErr, Python};
use alloc::borrow::Cow;
use core::ffi::CStr;
use std::ffi::CString;
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
use {
crate::{exceptions::PyValueError, ffi},
core::slice,
};
impl<'py> IntoPyObject<'py> for &CStr {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&str>::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
self.to_str()
.map_err(|e| PyUnicodeDecodeError::new_err_from_utf8(py, self.to_bytes(), e))?
.into_pyobject(py)
.map_err(|err| match err {})
}
}
impl<'py> IntoPyObject<'py> for CString {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&CStr>::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(&*self).into_pyobject(py)
}
}
impl<'py> IntoPyObject<'py> for &CString {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&CStr>::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(&**self).into_pyobject(py)
}
}
impl<'py> IntoPyObject<'py> for Cow<'_, CStr> {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&CStr>::OUTPUT_TYPE;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
impl<'py> IntoPyObject<'py> for &Cow<'_, CStr> {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&CStr>::OUTPUT_TYPE;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(&**self).into_pyobject(py)
}
}
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
impl<'a> FromPyObject<'a, '_> for &'a CStr {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
fn extract(obj: Borrowed<'a, '_, PyAny>) -> Result<Self, Self::Error> {
let obj = obj.cast::<PyString>()?;
let mut size = 0;
// SAFETY: obj is a PyString so we can safely call PyUnicode_AsUTF8AndSize
let ptr = unsafe { ffi::PyUnicode_AsUTF8AndSize(obj.as_ptr(), &mut size) };
if ptr.is_null() {
return Err(PyErr::fetch(obj.py()));
}
// SAFETY: PyUnicode_AsUTF8AndSize always returns a NUL-terminated string but size does not
// include the NUL terminator. So we add 1 to the size to include it.
let slice = unsafe { slice::from_raw_parts(ptr.cast(), size as usize + 1) };
CStr::from_bytes_with_nul(slice).map_err(|err| PyValueError::new_err(err.to_string()))
}
}
impl<'a> FromPyObject<'a, '_> for Cow<'a, CStr> {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
fn extract(obj: Borrowed<'a, '_, PyAny>) -> Result<Self, Self::Error> {
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
{
Ok(Cow::Borrowed(obj.extract::<&CStr>()?))
}
#[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
{
Ok(Cow::Owned(obj.extract::<CString>()?))
}
}
}
impl FromPyObject<'_, '_> for CString {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
{
Ok(obj.extract::<&CStr>()?.to_owned())
}
#[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
{
CString::new(&*obj.cast::<PyString>()?.to_cow()?).map_err(Into::into)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::string::PyStringMethods;
use crate::types::PyAnyMethods;
use crate::Python;
#[test]
fn test_into_pyobject() {
Python::attach(|py| {
let s = "Hello, Python!";
let cstr = CString::new(s).unwrap();
let py_string = cstr.as_c_str().into_pyobject(py).unwrap();
assert_eq!(py_string.to_cow().unwrap(), s);
let py_string = cstr.into_pyobject(py).unwrap();
assert_eq!(py_string.to_cow().unwrap(), s);
})
}
#[test]
fn test_extract_with_nul_error() {
Python::attach(|py| {
let s = "Hello\0Python";
let py_string = s.into_pyobject(py).unwrap();
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
{
let err = py_string.extract::<&CStr>();
assert!(err.is_err());
}
let err = py_string.extract::<CString>();
assert!(err.is_err());
})
}
#[test]
fn test_extract_cstr_and_cstring() {
Python::attach(|py| {
let s = "Hello, world!";
let cstr = CString::new(s).unwrap();
let py_string = cstr.as_c_str().into_pyobject(py).unwrap();
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
{
let extracted_cstr: &CStr = py_string.extract().unwrap();
assert_eq!(extracted_cstr.to_str().unwrap(), s);
}
let extracted_cstring: CString = py_string.extract().unwrap();
assert_eq!(extracted_cstring.to_str().unwrap(), s);
})
}
#[test]
fn test_cow_roundtrip() {
Python::attach(|py| {
let s = "Hello, world!";
let cstr = CString::new(s).unwrap();
let cow: Cow<'_, CStr> = Cow::Borrowed(cstr.as_c_str());
let py_string = cow.into_pyobject(py).unwrap();
assert_eq!(py_string.to_cow().unwrap(), s);
let roundtripped: Cow<'_, CStr> = py_string.extract().unwrap();
assert_eq!(roundtripped.as_ref(), cstr.as_c_str());
})
}
}
+175
View File
@@ -0,0 +1,175 @@
use crate::conversion::IntoPyObject;
use crate::exceptions::PyValueError;
#[cfg(feature = "experimental-inspect")]
use crate::inspect::{type_hint_identifier, type_hint_union, PyStaticExpr};
use crate::sync::PyOnceLock;
use crate::types::any::PyAnyMethods;
use crate::types::string::PyStringMethods;
use crate::types::PyType;
use crate::{intern, Borrowed, Bound, FromPyObject, Py, PyAny, PyErr, Python};
use core::net::{IpAddr, Ipv4Addr, Ipv6Addr};
impl FromPyObject<'_, '_> for IpAddr {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = type_hint_union!(
type_hint_identifier!("ipaddress", "IPv4Address"),
type_hint_identifier!("ipaddress", "IPv6Address")
);
fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
match obj.getattr(intern!(obj.py(), "packed")) {
Ok(packed) => {
if let Ok(packed) = packed.extract::<[u8; 4]>() {
Ok(IpAddr::V4(Ipv4Addr::from(packed)))
} else if let Ok(packed) = packed.extract::<[u8; 16]>() {
Ok(IpAddr::V6(Ipv6Addr::from(packed)))
} else {
Err(PyValueError::new_err("invalid packed length"))
}
}
Err(_) => {
// We don't have a .packed attribute, so we try to construct an IP from str().
obj.str()?.to_cow()?.parse().map_err(PyValueError::new_err)
}
}
}
}
impl<'py> IntoPyObject<'py> for Ipv4Addr {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("ipaddress", "IPv4Address");
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
static IPV4_ADDRESS: PyOnceLock<Py<PyType>> = PyOnceLock::new();
IPV4_ADDRESS
.import(py, "ipaddress", "IPv4Address")?
.call1((u32::from_be_bytes(self.octets()),))
}
}
impl<'py> IntoPyObject<'py> for &Ipv4Addr {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = Ipv4Addr::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
impl<'py> IntoPyObject<'py> for Ipv6Addr {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("ipaddress", "IPv6Address");
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
static IPV6_ADDRESS: PyOnceLock<Py<PyType>> = PyOnceLock::new();
IPV6_ADDRESS
.import(py, "ipaddress", "IPv6Address")?
.call1((u128::from_be_bytes(self.octets()),))
}
}
impl<'py> IntoPyObject<'py> for &Ipv6Addr {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = Ipv6Addr::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
impl<'py> IntoPyObject<'py> for IpAddr {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr =
type_hint_union!(&Ipv4Addr::OUTPUT_TYPE, &Ipv6Addr::OUTPUT_TYPE);
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
match self {
IpAddr::V4(ip) => ip.into_pyobject(py),
IpAddr::V6(ip) => ip.into_pyobject(py),
}
}
}
impl<'py> IntoPyObject<'py> for &IpAddr {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = IpAddr::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
#[cfg(test)]
mod test_ipaddr {
use core::str::FromStr;
use crate::types::PyString;
use super::*;
#[test]
fn test_roundtrip() {
Python::attach(|py| {
fn roundtrip(py: Python<'_>, ip: &str) {
let ip = IpAddr::from_str(ip).unwrap();
let py_cls = if ip.is_ipv4() {
"IPv4Address"
} else {
"IPv6Address"
};
let pyobj = ip.into_pyobject(py).unwrap();
let repr = pyobj.repr().unwrap();
let repr = repr.to_string_lossy();
assert_eq!(repr, format!("{py_cls}('{ip}')"));
let ip2: IpAddr = pyobj.extract().unwrap();
assert_eq!(ip, ip2);
}
roundtrip(py, "127.0.0.1");
roundtrip(py, "::1");
roundtrip(py, "0.0.0.0");
});
}
#[test]
fn test_from_pystring() {
Python::attach(|py| {
let py_str = PyString::new(py, "0:0:0:0:0:0:0:1");
let ip: IpAddr = py_str.extract().unwrap();
assert_eq!(ip, IpAddr::from_str("::1").unwrap());
let py_str = PyString::new(py, "invalid");
assert!(py_str.extract::<IpAddr>().is_err());
});
}
}
+246
View File
@@ -0,0 +1,246 @@
#[cfg(feature = "experimental-inspect")]
use crate::inspect::{type_hint_subscript, PyStaticExpr};
#[cfg(feature = "experimental-inspect")]
use crate::type_object::PyTypeInfo;
use crate::{
conversion::{FromPyObjectOwned, IntoPyObject},
instance::Bound,
types::{any::PyAnyMethods, dict::PyDictMethods, PyDict},
Borrowed, FromPyObject, PyAny, PyErr, Python,
};
use core::{cmp, hash};
use std::collections;
impl<'py, K, V, H> IntoPyObject<'py> for collections::HashMap<K, V, H>
where
K: IntoPyObject<'py> + cmp::Eq + hash::Hash,
V: IntoPyObject<'py>,
H: hash::BuildHasher,
{
type Target = PyDict;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr =
type_hint_subscript!(PyDict::TYPE_HINT, K::OUTPUT_TYPE, V::OUTPUT_TYPE);
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let dict = PyDict::new(py);
for (k, v) in self {
dict.set_item(k, v)?;
}
Ok(dict)
}
}
impl<'a, 'py, K, V, H> IntoPyObject<'py> for &'a collections::HashMap<K, V, H>
where
&'a K: IntoPyObject<'py> + cmp::Eq + hash::Hash,
&'a V: IntoPyObject<'py>,
H: hash::BuildHasher,
{
type Target = PyDict;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr =
type_hint_subscript!(PyDict::TYPE_HINT, <&K>::OUTPUT_TYPE, <&V>::OUTPUT_TYPE);
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let dict = PyDict::new(py);
for (k, v) in self {
dict.set_item(k, v)?;
}
Ok(dict)
}
}
impl<'py, K, V> IntoPyObject<'py> for collections::BTreeMap<K, V>
where
K: IntoPyObject<'py> + cmp::Eq,
V: IntoPyObject<'py>,
{
type Target = PyDict;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr =
type_hint_subscript!(PyDict::TYPE_HINT, K::OUTPUT_TYPE, V::OUTPUT_TYPE);
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let dict = PyDict::new(py);
for (k, v) in self {
dict.set_item(k, v)?;
}
Ok(dict)
}
}
impl<'a, 'py, K, V> IntoPyObject<'py> for &'a collections::BTreeMap<K, V>
where
&'a K: IntoPyObject<'py> + cmp::Eq,
&'a V: IntoPyObject<'py>,
K: 'a,
V: 'a,
{
type Target = PyDict;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr =
type_hint_subscript!(PyDict::TYPE_HINT, <&K>::OUTPUT_TYPE, <&V>::OUTPUT_TYPE);
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let dict = PyDict::new(py);
for (k, v) in self {
dict.set_item(k, v)?;
}
Ok(dict)
}
}
impl<'py, K, V, S> FromPyObject<'_, 'py> for collections::HashMap<K, V, S>
where
K: FromPyObjectOwned<'py> + cmp::Eq + hash::Hash,
V: FromPyObjectOwned<'py>,
S: hash::BuildHasher + Default,
{
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr =
type_hint_subscript!(&PyDict::TYPE_HINT, K::INPUT_TYPE, V::INPUT_TYPE);
fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {
let dict = ob.cast::<PyDict>()?;
let mut ret = collections::HashMap::with_capacity_and_hasher(dict.len(), S::default());
for (k, v) in dict.iter() {
ret.insert(
k.extract().map_err(Into::into)?,
v.extract().map_err(Into::into)?,
);
}
Ok(ret)
}
}
impl<'py, K, V> FromPyObject<'_, 'py> for collections::BTreeMap<K, V>
where
K: FromPyObjectOwned<'py> + cmp::Ord,
V: FromPyObjectOwned<'py>,
{
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr =
type_hint_subscript!(PyDict::TYPE_HINT, K::INPUT_TYPE, V::INPUT_TYPE);
fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result<Self, PyErr> {
let dict = ob.cast::<PyDict>()?;
let mut ret = collections::BTreeMap::new();
for (k, v) in dict.iter() {
ret.insert(
k.extract().map_err(Into::into)?,
v.extract().map_err(Into::into)?,
);
}
Ok(ret)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::collections::BTreeMap;
use std::collections::HashMap;
#[test]
fn test_hashmap_to_python() {
Python::attach(|py| {
let mut map = HashMap::<i32, i32>::new();
map.insert(1, 1);
let py_map = (&map).into_pyobject(py).unwrap();
assert_eq!(py_map.len(), 1);
assert!(
py_map
.get_item(1)
.unwrap()
.unwrap()
.extract::<i32>()
.unwrap()
== 1
);
assert_eq!(map, py_map.extract().unwrap());
});
}
#[test]
fn test_btreemap_to_python() {
Python::attach(|py| {
let mut map = BTreeMap::<i32, i32>::new();
map.insert(1, 1);
let py_map = (&map).into_pyobject(py).unwrap();
assert_eq!(py_map.len(), 1);
assert!(
py_map
.get_item(1)
.unwrap()
.unwrap()
.extract::<i32>()
.unwrap()
== 1
);
assert_eq!(map, py_map.extract().unwrap());
});
}
#[test]
fn test_hashmap_into_python() {
Python::attach(|py| {
let mut map = HashMap::<i32, i32>::new();
map.insert(1, 1);
let py_map = map.into_pyobject(py).unwrap();
assert_eq!(py_map.len(), 1);
assert!(
py_map
.get_item(1)
.unwrap()
.unwrap()
.extract::<i32>()
.unwrap()
== 1
);
});
}
#[test]
fn test_btreemap_into_py() {
Python::attach(|py| {
let mut map = BTreeMap::<i32, i32>::new();
map.insert(1, 1);
let py_map = map.into_pyobject(py).unwrap();
assert_eq!(py_map.len(), 1);
assert!(
py_map
.get_item(1)
.unwrap()
.unwrap()
.extract::<i32>()
.unwrap()
== 1
);
});
}
}
+14
View File
@@ -0,0 +1,14 @@
mod array;
mod cell;
mod cstring;
mod ipaddr;
mod map;
pub(crate) mod num;
mod option;
mod osstr;
mod path;
mod set;
mod slice;
mod string;
mod time;
mod vec;
File diff suppressed because it is too large. Load diff
+65
View File
@@ -0,0 +1,65 @@
#[cfg(feature = "experimental-inspect")]
use crate::inspect::{type_hint_union, PyStaticExpr};
#[cfg(feature = "experimental-inspect")]
use crate::type_object::PyTypeInfo;
#[cfg(feature = "experimental-inspect")]
use crate::types::PyNone;
use crate::{
conversion::IntoPyObject, types::any::PyAnyMethods, BoundObject, FromPyObject, PyAny, Python,
};
use crate::{Borrowed, Bound};
impl<'py, T> IntoPyObject<'py> for Option<T>
where
T: IntoPyObject<'py>,
{
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = T::Error;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = type_hint_union!(&T::OUTPUT_TYPE, PyNone::TYPE_HINT);
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
self.map_or_else(
|| Ok(py.None().into_bound(py)),
|val| {
val.into_pyobject(py)
.map(BoundObject::into_any)
.map(BoundObject::into_bound)
},
)
}
}
impl<'a, 'py, T> IntoPyObject<'py> for &'a Option<T>
where
&'a T: IntoPyObject<'py>,
{
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = <&'a T as IntoPyObject<'py>>::Error;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <Option<&T>>::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
self.as_ref().into_pyobject(py)
}
}
impl<'a, 'py, T> FromPyObject<'a, 'py> for Option<T>
where
T: FromPyObject<'a, 'py>,
{
type Error = T::Error;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = type_hint_union!(T::INPUT_TYPE, PyNone::TYPE_HINT);
fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
if obj.is_none() {
Ok(None)
} else {
obj.extract().map(Some)
}
}
}
+365
View File
@@ -0,0 +1,365 @@
use crate::conversion::IntoPyObject;
#[cfg(not(target_os = "wasi"))]
use crate::ffi;
#[cfg(not(target_os = "wasi"))]
use crate::ffi_ptr_ext::FfiPtrExt;
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
use crate::instance::Bound;
#[cfg(feature = "experimental-inspect")]
use crate::type_object::PyTypeInfo;
use crate::types::PyString;
#[cfg(any(unix, target_os = "emscripten"))]
use crate::types::{PyBytes, PyBytesMethods};
use crate::{Borrowed, FromPyObject, PyAny, PyErr, Python};
use alloc::borrow::Cow;
use core::convert::Infallible;
use std::ffi::{OsStr, OsString};
#[cfg(any(unix, target_os = "emscripten"))]
use std::os::unix::ffi::OsStrExt;
#[cfg(windows)]
use std::os::windows::ffi::OsStrExt;
impl<'py> IntoPyObject<'py> for &OsStr {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
// If the string is UTF-8, take the quick and easy shortcut
#[cfg(not(target_os = "wasi"))]
if let Some(valid_utf8_path) = self.to_str() {
return valid_utf8_path.into_pyobject(py);
}
#[cfg(target_os = "wasi")]
{
self.to_str()
.expect("wasi strings are UTF8")
.into_pyobject(py)
}
#[cfg(any(unix, target_os = "emscripten"))]
{
let bytes = self.as_bytes();
let ptr = bytes.as_ptr().cast();
let len = bytes.len() as ffi::Py_ssize_t;
// SAFETY: passing valid pointer to python API
unsafe {
// DecodeFSDefault automatically chooses an appropriate decoding mechanism to
// parse os strings losslessly (i.e. surrogateescape most of the time)
Ok(ffi::PyUnicode_DecodeFSDefaultAndSize(ptr, len)
.assume_owned(py)
.cast_into_unchecked())
}
}
#[cfg(windows)]
{
let wstr: Vec<u16> = self.encode_wide().collect();
// SAFETY: passing valid pointer to python API
unsafe {
// This will not panic because the data from encode_wide is well-formed Windows
// string data
Ok(
ffi::PyUnicode_FromWideChar(wstr.as_ptr(), wstr.len() as ffi::Py_ssize_t)
.assume_owned(py)
.cast_into_unchecked(),
)
}
}
}
}
impl<'py> IntoPyObject<'py> for &&OsStr {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&OsStr>::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
impl FromPyObject<'_, '_> for OsString {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
let pystring = ob.cast::<PyString>()?;
#[cfg(target_os = "wasi")]
{
Ok(pystring.to_cow()?.into_owned().into())
}
#[cfg(any(unix, target_os = "emscripten"))]
{
// Decode from Python's lossless bytes string representation back into raw bytes
// SAFETY: PyUnicode_EncodeFSDefault returns a new reference or null on error, known to
// be a `bytes` object, thread is attached to the interpreter
let fs_encoded_bytes = unsafe {
ffi::PyUnicode_EncodeFSDefault(pystring.as_ptr())
.assume_owned_or_err(ob.py())?
.cast_into_unchecked::<PyBytes>()
};
// Create an OsStr view into the raw bytes from Python
let os_str: &OsStr = OsStrExt::from_bytes(fs_encoded_bytes.as_bytes());
Ok(os_str.to_os_string())
}
#[cfg(windows)]
{
// Take the quick and easy shortcut if UTF-8
if let Ok(utf8_string) = pystring.to_cow() {
return Ok(utf8_string.into_owned().into());
}
// Get an owned allocated wide char buffer from PyString, which we have to deallocate
// ourselves
// SAFETY: passing valid pointer to python API
let size =
unsafe { ffi::PyUnicode_AsWideChar(pystring.as_ptr(), core::ptr::null_mut(), 0) };
crate::err::error_on_minusone(ob.py(), size)?;
debug_assert!(
size > 0,
"PyUnicode_AsWideChar should return at least 1 for null terminator"
);
let size = size - 1; // exclude null terminator
let mut buffer = vec![0; size as usize];
// SAFETY: passing valid pointer to python API
let bytes_read =
unsafe { ffi::PyUnicode_AsWideChar(pystring.as_ptr(), buffer.as_mut_ptr(), size) };
assert_eq!(bytes_read, size);
// Copy wide char buffer into OsString
let os_string = std::os::windows::ffi::OsStringExt::from_wide(&buffer);
Ok(os_string)
}
}
}
impl<'py> IntoPyObject<'py> for Cow<'_, OsStr> {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&OsStr>::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
impl<'py> IntoPyObject<'py> for &Cow<'_, OsStr> {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&OsStr>::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(&**self).into_pyobject(py)
}
}
impl<'a> FromPyObject<'a, '_> for Cow<'a, OsStr> {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = OsString::INPUT_TYPE;
fn extract(obj: Borrowed<'a, '_, PyAny>) -> Result<Self, Self::Error> {
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
if let Ok(s) = obj.extract::<&str>() {
return Ok(Cow::Borrowed(s.as_ref()));
}
obj.extract::<OsString>().map(Cow::Owned)
}
}
impl<'py> IntoPyObject<'py> for OsString {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&OsStr>::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
self.as_os_str().into_pyobject(py)
}
}
impl<'py> IntoPyObject<'py> for &OsString {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&OsStr>::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
self.as_os_str().into_pyobject(py)
}
}
#[cfg(test)]
mod tests {
#[cfg(target_os = "wasi")]
use crate::exceptions::PyFileNotFoundError;
use crate::types::{PyAnyMethods, PyString, PyStringMethods};
use crate::{Bound, BoundObject, IntoPyObject, Python};
use alloc::borrow::Cow;
use core::fmt::Debug;
use std::ffi::{OsStr, OsString};
#[cfg(any(unix, target_os = "emscripten"))]
use std::os::unix::ffi::OsStringExt;
#[cfg(windows)]
use std::os::windows::ffi::OsStringExt;
#[test]
#[cfg(any(unix, target_os = "emscripten"))]
fn test_non_utf8_conversion() {
Python::attach(|py| {
use std::os::unix::ffi::OsStrExt;
// this is not valid UTF-8
let payload = &[250, 251, 252, 253, 254, 255, 0, 255];
let os_str = OsStr::from_bytes(payload);
// do a roundtrip into Pythonland and back and compare
let py_str = os_str.into_pyobject(py).unwrap();
let os_str_2: OsString = py_str.extract().unwrap();
assert_eq!(os_str, os_str_2);
});
}
#[test]
#[cfg(target_os = "wasi")]
fn test_extract_non_utf8_wasi_should_error() {
Python::attach(|py| {
// Non utf-8 strings are not valid wasi paths
let open_result = py.run(c"open('\\udcff', 'rb')", None, None).unwrap_err();
assert!(
!open_result.is_instance_of::<PyFileNotFoundError>(py),
"Opening invalid utf8 will error with OSError, not FileNotFoundError"
);
// Create a Python string with not valid UTF-8: &[255]
let py_str = py.eval(c"'\\udcff'", None, None).unwrap();
assert!(
py_str.extract::<OsString>().is_err(),
"Extracting invalid UTF-8 as OsString should error"
);
});
}
#[test]
fn test_intopyobject_roundtrip() {
Python::attach(|py| {
fn test_roundtrip<'py, T>(py: Python<'py>, obj: T)
where
T: IntoPyObject<'py> + AsRef<OsStr> + Debug + Clone,
T::Error: Debug,
{
let pyobject = obj.clone().into_pyobject(py).unwrap().into_any();
let pystring = pyobject.as_borrowed().cast::<PyString>().unwrap();
assert_eq!(pystring.to_string_lossy(), obj.as_ref().to_string_lossy());
let roundtripped_obj: OsString = pystring.extract().unwrap();
assert_eq!(obj.as_ref(), roundtripped_obj.as_os_str());
}
let os_str = OsStr::new("Hello\0\n🐍");
test_roundtrip::<&OsStr>(py, os_str);
test_roundtrip::<Cow<'_, OsStr>>(py, Cow::Borrowed(os_str));
test_roundtrip::<Cow<'_, OsStr>>(py, Cow::Owned(os_str.to_os_string()));
test_roundtrip::<OsString>(py, os_str.to_os_string());
});
}
#[test]
#[cfg(windows)]
fn test_windows_non_utf8_osstring_roundtrip() {
use std::os::windows::ffi::{OsStrExt, OsStringExt};
Python::attach(|py| {
// Example: Unpaired surrogate (0xD800) is not valid UTF-8, but valid in Windows OsString
let wide: &[u16] = &['A' as u16, 0xD800, 'B' as u16]; // 'A', unpaired surrogate, 'B'
let os_str = OsString::from_wide(wide);
assert_eq!(os_str.to_string_lossy(), "A�B");
// This cannot be represented as UTF-8, so .to_str() would return None
assert!(os_str.to_str().is_none());
// Convert to Python and back
let py_str = os_str.as_os_str().into_pyobject(py).unwrap();
let os_str_2 = py_str.extract::<OsString>().unwrap();
// The roundtrip should preserve the original wide data
assert_eq!(os_str, os_str_2);
// Show that encode_wide is necessary: direct UTF-8 conversion would lose information
let encoded: Vec<u16> = os_str.encode_wide().collect();
assert_eq!(encoded, wide);
});
}
#[test]
fn test_extract_cow() {
Python::attach(|py| {
fn test_extract<'py, T>(py: Python<'py>, input: &T, is_borrowed: bool)
where
for<'a> &'a T: IntoPyObject<'py, Output = Bound<'py, PyString>>,
for<'a> <&'a T as IntoPyObject<'py>>::Error: Debug,
T: AsRef<OsStr> + ?Sized,
{
let pystring = input.into_pyobject(py).unwrap();
let cow: Cow<'_, OsStr> = pystring.extract().unwrap();
assert_eq!(cow, input.as_ref());
assert_eq!(is_borrowed, matches!(cow, Cow::Borrowed(_)));
}
// On Python 3.10+ or when not using the limited API, we can borrow strings from python
let can_borrow_str = cfg!(any(Py_3_10, not(Py_LIMITED_API)));
// This can be borrowed because it is valid UTF-8
test_extract::<str>(py, "Hello\0\n🐍", can_borrow_str);
test_extract::<str>(py, "Hello, world!", can_borrow_str);
#[cfg(windows)]
let os_str = {
// 'A', unpaired surrogate, 'B'
OsString::from_wide(&['A' as u16, 0xD800, 'B' as u16])
};
#[cfg(any(unix, target_os = "emscripten"))]
let os_str = { OsString::from_vec(vec![250, 251, 252, 253, 254, 255, 0, 255]) };
// This cannot be borrowed because it is not valid UTF-8
#[cfg(any(windows, unix, target_os = "emscripten"))]
test_extract::<OsStr>(py, &os_str, false);
});
}
}
+234
View File
@@ -0,0 +1,234 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
use crate::conversion::IntoPyObject;
use crate::ffi_ptr_ext::FfiPtrExt;
#[cfg(feature = "experimental-inspect")]
use crate::inspect::{type_hint_identifier, type_hint_subscript, type_hint_union, PyStaticExpr};
use crate::sync::PyOnceLock;
use crate::types::any::PyAnyMethods;
use crate::{ffi, Borrowed, Bound, FromPyObject, Py, PyAny, PyErr, Python};
use alloc::borrow::Cow;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
impl FromPyObject<'_, '_> for PathBuf {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = type_hint_union!(
OsString::INPUT_TYPE,
type_hint_subscript!(
type_hint_identifier!("os", "PathLike"),
OsString::INPUT_TYPE
)
);
fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
// We use os.fspath to get the underlying path as bytes or str
let path = unsafe { ffi::PyOS_FSPath(ob.as_ptr()).assume_owned_or_err(ob.py())? };
Ok(path.extract::<OsString>()?.into())
}
}
impl<'py> IntoPyObject<'py> for &Path {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("pathlib", "Path");
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
static PY_PATH: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
PY_PATH
.import(py, "pathlib", "Path")?
.call((self.as_os_str(),), None)
}
}
impl<'py> IntoPyObject<'py> for &&Path {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&Path>::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
impl<'py> IntoPyObject<'py> for Cow<'_, Path> {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&Path>::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
impl<'py> IntoPyObject<'py> for &Cow<'_, Path> {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&Path>::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(&**self).into_pyobject(py)
}
}
impl<'a> FromPyObject<'a, '_> for Cow<'a, Path> {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = PathBuf::INPUT_TYPE;
fn extract(obj: Borrowed<'a, '_, PyAny>) -> Result<Self, Self::Error> {
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
if let Ok(s) = obj.extract::<&str>() {
return Ok(Cow::Borrowed(s.as_ref()));
}
obj.extract::<PathBuf>().map(Cow::Owned)
}
}
impl<'py> IntoPyObject<'py> for PathBuf {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&Path>::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(&self).into_pyobject(py)
}
}
impl<'py> IntoPyObject<'py> for &PathBuf {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&Path>::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(&**self).into_pyobject(py)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
types::{PyAnyMethods, PyString},
IntoPyObjectExt,
};
use core::fmt::Debug;
#[cfg(not(target_os = "wasi"))]
use std::ffi::OsStr;
#[cfg(any(unix, target_os = "emscripten"))]
use std::os::unix::ffi::OsStringExt;
#[cfg(windows)]
use std::os::windows::ffi::OsStringExt;
#[test]
#[cfg(any(unix, target_os = "emscripten"))]
fn test_non_utf8_conversion() {
Python::attach(|py| {
use std::os::unix::ffi::OsStrExt;
// this is not valid UTF-8
let payload = &[250, 251, 252, 253, 254, 255, 0, 255];
let path = Path::new(OsStr::from_bytes(payload));
// do a roundtrip into Pythonland and back and compare
let py_str = path.into_pyobject(py).unwrap();
let path_2: PathBuf = py_str.extract().unwrap();
assert_eq!(path, path_2);
});
}
#[test]
fn test_intopyobject_roundtrip() {
Python::attach(|py| {
fn test_roundtrip<'py, T>(py: Python<'py>, obj: T)
where
T: IntoPyObject<'py> + AsRef<Path> + Debug + Clone,
T::Error: Debug,
{
let pyobject = obj.clone().into_bound_py_any(py).unwrap();
let roundtripped_obj: PathBuf = pyobject.extract().unwrap();
assert_eq!(obj.as_ref(), roundtripped_obj.as_path());
}
let path = Path::new("Hello\0\n🐍");
test_roundtrip::<&Path>(py, path);
test_roundtrip::<Cow<'_, Path>>(py, Cow::Borrowed(path));
test_roundtrip::<Cow<'_, Path>>(py, Cow::Owned(path.to_path_buf()));
test_roundtrip::<PathBuf>(py, path.to_path_buf());
});
}
#[test]
fn test_from_pystring() {
Python::attach(|py| {
let path = "Hello\0\n🐍";
let pystring = PyString::new(py, path);
let roundtrip: PathBuf = pystring.extract().unwrap();
assert_eq!(roundtrip, Path::new(path));
});
}
#[test]
fn test_extract_cow() {
Python::attach(|py| {
fn test_extract<'py, T>(py: Python<'py>, path: &T, is_borrowed: bool)
where
for<'a> &'a T: IntoPyObject<'py, Output = Bound<'py, PyString>>,
for<'a> <&'a T as IntoPyObject<'py>>::Error: Debug,
T: AsRef<Path> + ?Sized,
{
let pystring = path.into_pyobject(py).unwrap();
let cow: Cow<'_, Path> = pystring.extract().unwrap();
assert_eq!(cow, path.as_ref());
assert_eq!(is_borrowed, matches!(cow, Cow::Borrowed(_)));
}
// On Python 3.10+ or when not using the limited API, we can borrow strings from python
let can_borrow_str = cfg!(any(Py_3_10, not(Py_LIMITED_API)));
// This can be borrowed because it is valid UTF-8
test_extract::<str>(py, "Hello\0\n🐍", can_borrow_str);
test_extract::<str>(py, "Hello, world!", can_borrow_str);
#[cfg(windows)]
let os_str = {
// 'A', unpaired surrogate, 'B'
OsString::from_wide(&['A' as u16, 0xD800, 'B' as u16])
};
#[cfg(any(unix, target_os = "emscripten"))]
let os_str = { OsString::from_vec(vec![250, 251, 252, 253, 254, 255, 0, 255]) };
// This cannot be borrowed because it is not valid UTF-8
#[cfg(any(unix, windows, target_os = "emscripten"))]
test_extract::<OsStr>(py, &os_str, false);
});
}
}
+187
View File
@@ -0,0 +1,187 @@
use core::{cmp, hash};
use std::collections;
#[cfg(feature = "experimental-inspect")]
use crate::inspect::{type_hint_subscript, PyStaticExpr};
#[cfg(feature = "experimental-inspect")]
use crate::type_object::PyTypeInfo;
use crate::{
conversion::{FromPyObjectOwned, IntoPyObject},
types::{
any::PyAnyMethods, frozenset::PyFrozenSetMethods, set::PySetMethods, PyFrozenSet, PySet,
},
Borrowed, Bound, FromPyObject, PyAny, PyErr, Python,
};
impl<'py, K, S> IntoPyObject<'py> for collections::HashSet<K, S>
where
K: IntoPyObject<'py> + Eq + hash::Hash,
S: hash::BuildHasher + Default,
{
type Target = PySet;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = type_hint_subscript!(PySet::TYPE_HINT, K::OUTPUT_TYPE);
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
PySet::new(py, self)
}
}
impl<'a, 'py, K, H> IntoPyObject<'py> for &'a collections::HashSet<K, H>
where
&'a K: IntoPyObject<'py> + Eq + hash::Hash,
H: hash::BuildHasher,
{
type Target = PySet;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = type_hint_subscript!(PySet::TYPE_HINT, <&K>::OUTPUT_TYPE);
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
PySet::new(py, self)
}
}
impl<'py, K, S> FromPyObject<'_, 'py> for collections::HashSet<K, S>
where
K: FromPyObjectOwned<'py> + cmp::Eq + hash::Hash,
S: hash::BuildHasher + Default,
{
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = type_hint_subscript!(PySet::TYPE_HINT, K::INPUT_TYPE);
fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {
match ob.cast::<PySet>() {
Ok(set) => set
.iter()
.map(|any| any.extract().map_err(Into::into))
.collect(),
Err(err) => {
if let Ok(frozen_set) = ob.cast::<PyFrozenSet>() {
frozen_set
.iter()
.map(|any| any.extract().map_err(Into::into))
.collect()
} else {
Err(PyErr::from(err))
}
}
}
}
}
impl<'py, K> IntoPyObject<'py> for collections::BTreeSet<K>
where
K: IntoPyObject<'py> + cmp::Ord,
{
type Target = PySet;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = type_hint_subscript!(PySet::TYPE_HINT, K::OUTPUT_TYPE);
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
PySet::new(py, self)
}
}
impl<'a, 'py, K> IntoPyObject<'py> for &'a collections::BTreeSet<K>
where
&'a K: IntoPyObject<'py> + cmp::Ord,
K: 'a,
{
type Target = PySet;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = type_hint_subscript!(PySet::TYPE_HINT, <&K>::OUTPUT_TYPE);
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
PySet::new(py, self)
}
}
impl<'py, K> FromPyObject<'_, 'py> for collections::BTreeSet<K>
where
K: FromPyObjectOwned<'py> + cmp::Ord,
{
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = type_hint_subscript!(PySet::TYPE_HINT, K::INPUT_TYPE);
fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {
match ob.cast::<PySet>() {
Ok(set) => set
.iter()
.map(|any| any.extract().map_err(Into::into))
.collect(),
Err(err) => {
if let Ok(frozen_set) = ob.cast::<PyFrozenSet>() {
frozen_set
.iter()
.map(|any| any.extract().map_err(Into::into))
.collect()
} else {
Err(PyErr::from(err))
}
}
}
}
}
#[cfg(test)]
mod tests {
use crate::types::{any::PyAnyMethods, PyFrozenSet, PySet};
use crate::{IntoPyObject, Python};
use alloc::collections::BTreeSet;
use std::collections::HashSet;
#[test]
fn test_extract_hashset() {
Python::attach(|py| {
let set = PySet::new(py, [1, 2, 3, 4, 5]).unwrap();
let hash_set: HashSet<usize> = set.extract().unwrap();
assert_eq!(hash_set, [1, 2, 3, 4, 5].iter().copied().collect());
let set = PyFrozenSet::new(py, [1, 2, 3, 4, 5]).unwrap();
let hash_set: HashSet<usize> = set.extract().unwrap();
assert_eq!(hash_set, [1, 2, 3, 4, 5].iter().copied().collect());
});
}
#[test]
fn test_extract_btreeset() {
Python::attach(|py| {
let set = PySet::new(py, [1, 2, 3, 4, 5]).unwrap();
let hash_set: BTreeSet<usize> = set.extract().unwrap();
assert_eq!(hash_set, [1, 2, 3, 4, 5].iter().copied().collect());
let set = PyFrozenSet::new(py, [1, 2, 3, 4, 5]).unwrap();
let hash_set: BTreeSet<usize> = set.extract().unwrap();
assert_eq!(hash_set, [1, 2, 3, 4, 5].iter().copied().collect());
});
}
#[test]
fn test_set_into_pyobject() {
Python::attach(|py| {
let bt: BTreeSet<u64> = [1, 2, 3, 4, 5].iter().cloned().collect();
let hs: HashSet<u64> = [1, 2, 3, 4, 5].iter().cloned().collect();
let bto = (&bt).into_pyobject(py).unwrap();
let hso = (&hs).into_pyobject(py).unwrap();
assert_eq!(bt, bto.extract().unwrap());
assert_eq!(hs, hso.extract().unwrap());
});
}
}
+169
View File
@@ -0,0 +1,169 @@
use alloc::borrow::Cow;
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
#[cfg(feature = "experimental-inspect")]
use crate::type_object::PyTypeInfo;
use crate::{
conversion::IntoPyObject, types::PyBytes, Bound, CastError, PyAny, PyErr, PyResult, Python,
};
impl<'a, 'py, T> IntoPyObject<'py> for &'a [T]
where
&'a T: IntoPyObject<'py>,
{
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&T>::SEQUENCE_OUTPUT_TYPE;
/// Turns [`&[u8]`](core::slice) into [`PyBytes`], all other `T`s will be turned into a [`PyList`]
///
/// [`PyBytes`]: crate::types::PyBytes
/// [`PyList`]: crate::types::PyList
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
<&T>::borrowed_sequence_into_pyobject(self, py, crate::conversion::private::Token)
}
}
impl<'a, 'py> crate::conversion::FromPyObject<'a, 'py> for &'a [u8] {
type Error = CastError<'a, 'py>;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = PyBytes::TYPE_HINT;
fn extract(obj: crate::Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
Ok(obj.cast::<PyBytes>()?.as_bytes())
}
}
/// Special-purpose trait impl to efficiently handle both `bytes` and `bytearray`
///
/// If the source object is a `bytes` object, the `Cow` will be borrowed and
/// pointing into the source object, and no copying or heap allocations will happen.
/// If it is a `bytearray`, its contents will be copied to an owned `Cow`.
impl<'a, 'py> crate::conversion::FromPyObject<'a, 'py> for Cow<'a, [u8]> {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = Vec::<u8>::INPUT_TYPE;
fn extract(ob: crate::Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
Ok(if let Ok(bytes) = ob.cast::<PyBytes>() {
Cow::Borrowed(bytes.as_bytes()) // It's immutable, we can take a slice
} else {
Cow::Owned(Vec::extract(ob)?) // Not possible to take a slice, we have to build a Vec<u8>
})
}
}
impl<'py, T> IntoPyObject<'py> for Cow<'_, [T]>
where
T: Clone,
for<'a> &'a T: IntoPyObject<'py>,
{
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&T>::SEQUENCE_OUTPUT_TYPE;
/// Turns `Cow<[u8]>` into [`PyBytes`], all other `T`s will be turned into a [`PyList`]
///
/// [`PyBytes`]: crate::types::PyBytes
/// [`PyList`]: crate::types::PyList
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
<&T>::borrowed_sequence_into_pyobject(self.as_ref(), py, crate::conversion::private::Token)
}
}
#[cfg(test)]
mod tests {
use alloc::borrow::Cow;
use crate::{
conversion::IntoPyObject,
types::{any::PyAnyMethods, PyBytes, PyBytesMethods, PyList},
Python,
};
#[test]
fn test_extract_bytes() {
Python::attach(|py| {
let py_bytes = py.eval(c"b'Hello Python'", None, None).unwrap();
let bytes: &[u8] = py_bytes.extract().unwrap();
assert_eq!(bytes, b"Hello Python");
});
}
#[test]
fn test_cow_impl() {
Python::attach(|py| {
let bytes = py.eval(cr#"b"foobar""#, None, None).unwrap();
let cow = bytes.extract::<Cow<'_, [u8]>>().unwrap();
assert_eq!(cow, Cow::<[u8]>::Borrowed(b"foobar"));
let byte_array = py.eval(cr#"bytearray(b"foobar")"#, None, None).unwrap();
let cow = byte_array.extract::<Cow<'_, [u8]>>().unwrap();
assert_eq!(cow, Cow::<[u8]>::Owned(b"foobar".to_vec()));
let something_else_entirely = py.eval(c"42", None, None).unwrap();
something_else_entirely
.extract::<Cow<'_, [u8]>>()
.unwrap_err();
let cow = Cow::<[u8]>::Borrowed(b"foobar").into_pyobject(py).unwrap();
assert!(cow.is_instance_of::<PyBytes>());
let cow = Cow::<[u8]>::Owned(b"foobar".to_vec())
.into_pyobject(py)
.unwrap();
assert!(cow.is_instance_of::<PyBytes>());
});
}
#[test]
fn test_slice_intopyobject_impl() {
Python::attach(|py| {
let bytes: &[u8] = b"foobar";
let obj = bytes.into_pyobject(py).unwrap();
assert!(obj.is_instance_of::<PyBytes>());
let obj = obj.cast_into::<PyBytes>().unwrap();
assert_eq!(obj.as_bytes(), bytes);
let nums: &[u16] = &[0, 1, 2, 3];
let obj = nums.into_pyobject(py).unwrap();
assert!(obj.is_instance_of::<PyList>());
});
}
#[test]
fn test_cow_intopyobject_impl() {
Python::attach(|py| {
let borrowed_bytes = Cow::<[u8]>::Borrowed(b"foobar");
let obj = borrowed_bytes.clone().into_pyobject(py).unwrap();
assert!(obj.is_instance_of::<PyBytes>());
let obj = obj.cast_into::<PyBytes>().unwrap();
assert_eq!(obj.as_bytes(), &*borrowed_bytes);
let owned_bytes = Cow::<[u8]>::Owned(b"foobar".to_vec());
let obj = owned_bytes.clone().into_pyobject(py).unwrap();
assert!(obj.is_instance_of::<PyBytes>());
let obj = obj.cast_into::<PyBytes>().unwrap();
assert_eq!(obj.as_bytes(), &*owned_bytes);
let borrowed_nums = Cow::<[u16]>::Borrowed(&[0, 1, 2, 3]);
let obj = borrowed_nums.into_pyobject(py).unwrap();
assert!(obj.is_instance_of::<PyList>());
let owned_nums = Cow::<[u16]>::Owned(vec![0, 1, 2, 3]);
let obj = owned_nums.into_pyobject(py).unwrap();
assert!(obj.is_instance_of::<PyList>());
});
}
}
+267
View File
@@ -0,0 +1,267 @@
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
#[cfg(feature = "experimental-inspect")]
use crate::type_object::PyTypeInfo;
use crate::{
conversion::IntoPyObject, instance::Bound, types::PyString, Borrowed, FromPyObject, PyAny,
PyErr, Python,
};
use alloc::borrow::Cow;
use core::convert::Infallible;
impl<'py> IntoPyObject<'py> for &str {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = String::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(PyString::new(py, self))
}
}
impl<'py> IntoPyObject<'py> for &&str {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = String::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
impl<'py> IntoPyObject<'py> for Cow<'_, str> {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = String::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
impl<'py> IntoPyObject<'py> for &Cow<'_, str> {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = String::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(&**self).into_pyobject(py)
}
}
impl<'py> IntoPyObject<'py> for char {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = String::OUTPUT_TYPE;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let mut bytes = [0u8; 4];
Ok(PyString::new(py, self.encode_utf8(&mut bytes)))
}
}
impl<'py> IntoPyObject<'py> for &char {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = String::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
impl<'py> IntoPyObject<'py> for String {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(PyString::new(py, &self))
}
}
impl<'py> IntoPyObject<'py> for &String {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = String::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(PyString::new(py, self))
}
}
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
impl<'a> FromPyObject<'a, '_> for &'a str {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
fn extract(ob: Borrowed<'a, '_, PyAny>) -> Result<Self, Self::Error> {
ob.cast::<PyString>()?.to_str()
}
}
impl<'a> FromPyObject<'a, '_> for Cow<'a, str> {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
fn extract(ob: Borrowed<'a, '_, PyAny>) -> Result<Self, Self::Error> {
ob.cast::<PyString>()?.to_cow()
}
}
/// Allows extracting strings from Python objects.
/// Accepts Python `str` and `unicode` objects.
impl FromPyObject<'_, '_> for String {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
obj.cast::<PyString>()?.to_cow().map(Cow::into_owned)
}
}
impl FromPyObject<'_, '_> for char {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
let s = obj.cast::<PyString>()?.to_cow()?;
let mut iter = s.chars();
if let (Some(ch), None) = (iter.next(), iter.next()) {
Ok(ch)
} else {
Err(crate::exceptions::PyValueError::new_err(
"expected a string of length 1",
))
}
}
}
#[cfg(test)]
mod tests {
use crate::types::any::PyAnyMethods;
use crate::{IntoPyObject, Python};
use alloc::borrow::Cow;
#[test]
fn test_cow_into_pyobject() {
Python::attach(|py| {
let s = "Hello Python";
let py_string = Cow::Borrowed(s).into_pyobject(py).unwrap();
assert_eq!(s, py_string.extract::<Cow<'_, str>>().unwrap());
let py_string = Cow::<str>::Owned(s.into()).into_pyobject(py).unwrap();
assert_eq!(s, py_string.extract::<Cow<'_, str>>().unwrap());
})
}
#[test]
fn test_non_bmp() {
Python::attach(|py| {
let s = "\u{1F30F}";
let py_string = s.into_pyobject(py).unwrap();
assert_eq!(s, py_string.extract::<String>().unwrap());
})
}
#[test]
fn test_extract_str() {
Python::attach(|py| {
let s = "Hello Python";
let py_string = s.into_pyobject(py).unwrap();
let s2: Cow<'_, str> = py_string.extract().unwrap();
assert_eq!(s, s2);
})
}
#[test]
fn test_extract_char() {
Python::attach(|py| {
let ch = '😃';
let py_string = ch.into_pyobject(py).unwrap();
let ch2: char = py_string.extract().unwrap();
assert_eq!(ch, ch2);
})
}
#[test]
fn test_extract_char_err() {
Python::attach(|py| {
let s = "Hello Python";
let py_string = s.into_pyobject(py).unwrap();
let err: crate::PyResult<char> = py_string.extract();
assert!(err
.unwrap_err()
.to_string()
.contains("expected a string of length 1"));
})
}
#[test]
fn test_string_into_pyobject() {
Python::attach(|py| {
let s = "Hello Python";
let s2 = s.to_owned();
let s3 = &s2;
assert_eq!(
s,
s3.into_pyobject(py)
.unwrap()
.extract::<Cow<'_, str>>()
.unwrap()
);
assert_eq!(
s,
s2.into_pyobject(py)
.unwrap()
.extract::<Cow<'_, str>>()
.unwrap()
);
assert_eq!(
s,
s.into_pyobject(py)
.unwrap()
.extract::<Cow<'_, str>>()
.unwrap()
);
})
}
}
+405
View File
@@ -0,0 +1,405 @@
use crate::conversion::IntoPyObject;
use crate::exceptions::{PyOverflowError, PyValueError};
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
#[cfg(Py_LIMITED_API)]
use crate::intern;
use crate::sync::PyOnceLock;
#[cfg(feature = "experimental-inspect")]
use crate::type_object::PyTypeInfo;
use crate::types::any::PyAnyMethods;
#[cfg(not(Py_LIMITED_API))]
use crate::types::PyDeltaAccess;
use crate::types::{PyDateTime, PyDelta, PyTzInfo};
use crate::{Borrowed, Bound, FromPyObject, Py, PyAny, PyErr, PyResult, Python};
use core::time::Duration;
use std::time::{SystemTime, UNIX_EPOCH};
const SECONDS_PER_DAY: u64 = 24 * 60 * 60;
impl FromPyObject<'_, '_> for Duration {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = PyDelta::TYPE_HINT;
fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
let delta = obj.cast::<PyDelta>()?;
#[cfg(not(Py_LIMITED_API))]
let (days, seconds, microseconds) = {
(
delta.get_days(),
delta.get_seconds(),
delta.get_microseconds(),
)
};
#[cfg(Py_LIMITED_API)]
let (days, seconds, microseconds): (i32, i32, i32) = {
let py = delta.py();
(
delta.getattr(intern!(py, "days"))?.extract()?,
delta.getattr(intern!(py, "seconds"))?.extract()?,
delta.getattr(intern!(py, "microseconds"))?.extract()?,
)
};
// We cast
let days = u64::try_from(days).map_err(|_| {
PyValueError::new_err(
"It is not possible to convert a negative timedelta to a Rust Duration",
)
})?;
let seconds = u64::try_from(seconds).unwrap(); // 0 <= seconds < 3600*24
let microseconds = u32::try_from(microseconds).unwrap(); // 0 <= microseconds < 1000000
// We convert
let total_seconds = days * SECONDS_PER_DAY + seconds; // We casted from i32, this can't overflow
let nanoseconds = microseconds.checked_mul(1_000).unwrap(); // 0 <= microseconds < 1000000
Ok(Duration::new(total_seconds, nanoseconds))
}
}
impl<'py> IntoPyObject<'py> for Duration {
type Target = PyDelta;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = PyDelta::TYPE_HINT;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let days = self.as_secs() / SECONDS_PER_DAY;
let seconds = self.as_secs() % SECONDS_PER_DAY;
let microseconds = self.subsec_micros();
PyDelta::new(
py,
days.try_into()?,
seconds.try_into()?,
microseconds.try_into()?,
false,
)
}
}
impl<'py> IntoPyObject<'py> for &Duration {
type Target = PyDelta;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = Duration::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
// Conversions between SystemTime and datetime do not rely on the floating point timestamp of the
// timestamp/fromtimestamp APIs to avoid possible precision loss but goes through the
// timedelta/core::time::Duration types by taking for reference point the UNIX epoch.
//
// TODO: it might be nice to investigate using timestamps anyway, at least when the datetime is a safe range.
impl FromPyObject<'_, '_> for SystemTime {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = PyDateTime::TYPE_HINT;
fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
let duration_since_unix_epoch: Duration = obj.sub(unix_epoch_py(obj.py())?)?.extract()?;
UNIX_EPOCH
.checked_add(duration_since_unix_epoch)
.ok_or_else(|| {
PyOverflowError::new_err("Overflow error when converting the time to Rust")
})
}
}
impl<'py> IntoPyObject<'py> for SystemTime {
type Target = PyDateTime;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = PyDateTime::TYPE_HINT;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let duration_since_unix_epoch =
self.duration_since(UNIX_EPOCH).unwrap().into_pyobject(py)?;
unix_epoch_py(py)?
.add(duration_since_unix_epoch)?
.cast_into()
.map_err(Into::into)
}
}
impl<'py> IntoPyObject<'py> for &SystemTime {
type Target = PyDateTime;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = SystemTime::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
fn unix_epoch_py(py: Python<'_>) -> PyResult<Borrowed<'_, '_, PyDateTime>> {
static UNIX_EPOCH: PyOnceLock<Py<PyDateTime>> = PyOnceLock::new();
Ok(UNIX_EPOCH
.get_or_try_init(py, || {
let utc = PyTzInfo::utc(py)?;
Ok::<_, PyErr>(PyDateTime::new(py, 1970, 1, 1, 0, 0, 0, 0, Some(&utc))?.into())
})?
.bind_borrowed(py))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::PyDict;
#[test]
fn test_duration_frompyobject() {
Python::attach(|py| {
assert_eq!(
new_timedelta(py, 0, 0, 0).extract::<Duration>().unwrap(),
Duration::new(0, 0)
);
assert_eq!(
new_timedelta(py, 1, 0, 0).extract::<Duration>().unwrap(),
Duration::new(86400, 0)
);
assert_eq!(
new_timedelta(py, 0, 1, 0).extract::<Duration>().unwrap(),
Duration::new(1, 0)
);
assert_eq!(
new_timedelta(py, 0, 0, 1).extract::<Duration>().unwrap(),
Duration::new(0, 1_000)
);
assert_eq!(
new_timedelta(py, 1, 1, 1).extract::<Duration>().unwrap(),
Duration::new(86401, 1_000)
);
assert_eq!(
timedelta_class(py)
.getattr("max")
.unwrap()
.extract::<Duration>()
.unwrap(),
Duration::new(86399999999999, 999999000)
);
});
}
#[test]
fn test_duration_frompyobject_negative() {
Python::attach(|py| {
assert_eq!(
new_timedelta(py, 0, -1, 0)
.extract::<Duration>()
.unwrap_err()
.to_string(),
"ValueError: It is not possible to convert a negative timedelta to a Rust Duration"
);
})
}
#[test]
fn test_duration_into_pyobject() {
Python::attach(|py| {
let assert_eq = |l: Bound<'_, PyAny>, r: Bound<'_, PyAny>| {
assert!(l.eq(r).unwrap());
};
assert_eq(
Duration::new(0, 0).into_pyobject(py).unwrap().into_any(),
new_timedelta(py, 0, 0, 0),
);
assert_eq(
Duration::new(86400, 0)
.into_pyobject(py)
.unwrap()
.into_any(),
new_timedelta(py, 1, 0, 0),
);
assert_eq(
Duration::new(1, 0).into_pyobject(py).unwrap().into_any(),
new_timedelta(py, 0, 1, 0),
);
assert_eq(
Duration::new(0, 1_000)
.into_pyobject(py)
.unwrap()
.into_any(),
new_timedelta(py, 0, 0, 1),
);
assert_eq(
Duration::new(0, 1).into_pyobject(py).unwrap().into_any(),
new_timedelta(py, 0, 0, 0),
);
assert_eq(
Duration::new(86401, 1_000)
.into_pyobject(py)
.unwrap()
.into_any(),
new_timedelta(py, 1, 1, 1),
);
assert_eq(
Duration::new(86399999999999, 999999000)
.into_pyobject(py)
.unwrap()
.into_any(),
timedelta_class(py).getattr("max").unwrap(),
);
});
}
#[test]
fn test_duration_into_pyobject_overflow() {
Python::attach(|py| {
assert!(Duration::MAX.into_pyobject(py).is_err());
})
}
#[test]
fn test_time_frompyobject() {
Python::attach(|py| {
assert_eq!(
new_datetime(py, 1970, 1, 1, 0, 0, 0, 0)
.extract::<SystemTime>()
.unwrap(),
UNIX_EPOCH
);
assert_eq!(
new_datetime(py, 2020, 2, 3, 4, 5, 6, 7)
.extract::<SystemTime>()
.unwrap(),
UNIX_EPOCH
.checked_add(Duration::new(1580702706, 7000))
.unwrap()
);
assert_eq!(
max_datetime(py).extract::<SystemTime>().unwrap(),
UNIX_EPOCH
.checked_add(Duration::new(253402300799, 999999000))
.unwrap()
);
});
}
#[test]
fn test_time_frompyobject_before_epoch() {
Python::attach(|py| {
assert_eq!(
new_datetime(py, 1950, 1, 1, 0, 0, 0, 0)
.extract::<SystemTime>()
.unwrap_err()
.to_string(),
"ValueError: It is not possible to convert a negative timedelta to a Rust Duration"
);
})
}
#[test]
fn test_time_intopyobject() {
Python::attach(|py| {
let assert_eq = |l: Bound<'_, PyDateTime>, r: Bound<'_, PyDateTime>| {
assert!(l.eq(r).unwrap());
};
assert_eq(
UNIX_EPOCH
.checked_add(Duration::new(1580702706, 7123))
.unwrap()
.into_pyobject(py)
.unwrap(),
new_datetime(py, 2020, 2, 3, 4, 5, 6, 7),
);
assert_eq(
UNIX_EPOCH
.checked_add(Duration::new(253402300799, 999999000))
.unwrap()
.into_pyobject(py)
.unwrap(),
max_datetime(py),
);
});
}
#[expect(clippy::too_many_arguments)]
fn new_datetime(
py: Python<'_>,
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
microsecond: u32,
) -> Bound<'_, PyDateTime> {
let utc = PyTzInfo::utc(py).unwrap();
PyDateTime::new(
py,
year,
month,
day,
hour,
minute,
second,
microsecond,
Some(&utc),
)
.unwrap()
}
fn max_datetime(py: Python<'_>) -> Bound<'_, PyDateTime> {
let naive_max = datetime_class(py).getattr("max").unwrap();
let kargs = PyDict::new(py);
kargs
.set_item("tzinfo", PyTzInfo::utc(py).unwrap())
.unwrap();
naive_max
.call_method("replace", (), Some(&kargs))
.unwrap()
.cast_into()
.unwrap()
}
#[test]
fn test_time_intopyobject_overflow() {
let big_system_time = UNIX_EPOCH
.checked_add(Duration::new(300000000000, 0))
.unwrap();
Python::attach(|py| {
assert!(big_system_time.into_pyobject(py).is_err());
})
}
fn new_timedelta(
py: Python<'_>,
days: i32,
seconds: i32,
microseconds: i32,
) -> Bound<'_, PyAny> {
timedelta_class(py)
.call1((days, seconds, microseconds))
.unwrap()
}
fn datetime_class(py: Python<'_>) -> Bound<'_, PyAny> {
py.import("datetime").unwrap().getattr("datetime").unwrap()
}
fn timedelta_class(py: Python<'_>) -> Bound<'_, PyAny> {
py.import("datetime").unwrap().getattr("timedelta").unwrap()
}
}
+181
View File
@@ -0,0 +1,181 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
#[cfg(feature = "experimental-inspect")]
use crate::inspect::{type_hint_subscript, PyStaticExpr};
use crate::{
conversion::{FromPyObject, FromPyObjectOwned, FromPyObjectSequence, IntoPyObject},
exceptions::PyTypeError,
ffi,
types::{PyAnyMethods, PySequence, PyString},
Borrowed, CastError, PyResult, PyTypeInfo,
};
use crate::{Bound, PyAny, PyErr, Python};
impl<'py, T> IntoPyObject<'py> for Vec<T>
where
T: IntoPyObject<'py>,
{
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = T::SEQUENCE_OUTPUT_TYPE;
/// Turns [`Vec<u8>`] into [`PyBytes`], all other `T`s will be turned into a [`PyList`]
///
/// [`PyBytes`]: crate::types::PyBytes
/// [`PyList`]: crate::types::PyList
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
T::owned_sequence_into_pyobject(self, py, crate::conversion::private::Token)
}
}
impl<'a, 'py, T> IntoPyObject<'py> for &'a Vec<T>
where
&'a T: IntoPyObject<'py>,
{
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = <&[T]>::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
// NB: we could actually not cast to `PyAny`, which would be nice for
// `&Vec<u8>`, but that'd be inconsistent with the `IntoPyObject` impl
// above which always returns a `PyAny` for `Vec<T>`.
self.as_slice().into_pyobject(py).map(Bound::into_any)
}
}
impl<'py, T> FromPyObject<'_, 'py> for Vec<T>
where
T: FromPyObjectOwned<'py>,
{
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = type_hint_subscript!(PySequence::TYPE_HINT, T::INPUT_TYPE);
fn extract(obj: Borrowed<'_, 'py, PyAny>) -> PyResult<Self> {
if let Some(extractor) = T::sequence_extractor(obj, crate::conversion::private::Token) {
return Ok(extractor.to_vec());
}
if obj.is_instance_of::<PyString>() {
return Err(PyTypeError::new_err("Can't extract `str` to `Vec`"));
}
extract_sequence(obj)
}
}
fn extract_sequence<'py, T>(obj: Borrowed<'_, 'py, PyAny>) -> PyResult<Vec<T>>
where
T: FromPyObjectOwned<'py>,
{
// Types that pass `PySequence_Check` usually implement enough of the sequence protocol
// to support this function and if not, we will only fail extraction safely.
if unsafe { ffi::PySequence_Check(obj.as_ptr()) } == 0 {
return Err(CastError::new(obj, PySequence::type_object(obj.py()).into_any()).into());
}
let mut v = Vec::with_capacity(obj.len().unwrap_or(0));
for item in obj.try_iter()? {
v.push(item?.extract::<T>().map_err(Into::into)?);
}
Ok(v)
}
#[cfg(test)]
mod tests {
use crate::conversion::IntoPyObject;
use crate::types::{PyAnyMethods, PyBytes, PyBytesMethods, PyList};
use crate::Python;
#[test]
fn test_vec_intopyobject_impl() {
Python::attach(|py| {
let bytes: Vec<u8> = b"foobar".to_vec();
let obj = bytes.clone().into_pyobject(py).unwrap();
assert!(obj.is_instance_of::<PyBytes>());
let obj = obj.cast_into::<PyBytes>().unwrap();
assert_eq!(obj.as_bytes(), &bytes);
let nums: Vec<u16> = vec![0, 1, 2, 3];
let obj = nums.into_pyobject(py).unwrap();
assert!(obj.is_instance_of::<PyList>());
});
}
#[test]
fn test_vec_reference_intopyobject_impl() {
Python::attach(|py| {
let bytes: Vec<u8> = b"foobar".to_vec();
let obj = (&bytes).into_pyobject(py).unwrap();
assert!(obj.is_instance_of::<PyBytes>());
let obj = obj.cast_into::<PyBytes>().unwrap();
assert_eq!(obj.as_bytes(), &bytes);
let nums: Vec<u16> = vec![0, 1, 2, 3];
let obj = (&nums).into_pyobject(py).unwrap();
assert!(obj.is_instance_of::<PyList>());
});
}
#[test]
fn test_strings_cannot_be_extracted_to_vec() {
Python::attach(|py| {
let v = "London Calling";
let ob = v.into_pyobject(py).unwrap();
assert!(ob.extract::<Vec<String>>().is_err());
assert!(ob.extract::<Vec<char>>().is_err());
});
}
#[test]
fn test_extract_bytes_to_vec() {
Python::attach(|py| {
let v: Vec<u8> = PyBytes::new(py, b"abc").extract().unwrap();
assert_eq!(v, b"abc");
});
}
#[test]
fn test_extract_tuple_to_vec() {
Python::attach(|py| {
let v: Vec<i32> = py.eval(c"(1, 2)", None, None).unwrap().extract().unwrap();
assert_eq!(v, [1, 2]);
});
}
#[test]
fn test_extract_range_to_vec() {
Python::attach(|py| {
let v: Vec<i32> = py
.eval(c"range(1, 5)", None, None)
.unwrap()
.extract()
.unwrap();
assert_eq!(v, [1, 2, 3, 4]);
});
}
#[test]
fn test_extract_bytearray_to_vec() {
Python::attach(|py| {
let v: Vec<u8> = py
.eval(c"bytearray(b'abc')", None, None)
.unwrap()
.extract()
.unwrap();
assert_eq!(v, b"abc");
});
}
}
File diff suppressed because it is too large. Load diff
+252
View File
@@ -0,0 +1,252 @@
#![cfg(feature = "uuid")]
//! Conversions to and from [uuid](https://docs.rs/uuid/latest/uuid/)'s [`Uuid`] and [`NonNilUuid`] types.
//!
//! This is useful for converting Python's uuid.UUID into and from a native Rust type.
//!
//! # Setup
//!
//! To use this feature, add to your **`Cargo.toml`**:
//!
//! ```toml
//! [dependencies]
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"uuid\"] }")]
//! uuid = "1.11.0"
//! ```
//!
//! Note that you must use a compatible version of uuid and PyO3.
//! The required uuid version may vary based on the version of PyO3.
//!
//! # Example
//!
//! Rust code to create a function that parses a UUID string and returns it as a `Uuid`:
//!
//! ```rust,no_run
//! use pyo3::prelude::*;
//! use pyo3::exceptions::PyValueError;
//! use uuid::Uuid;
//!
//! /// Parse a UUID from a string.
//! #[pyfunction]
//! fn get_uuid_from_str(s: &str) -> PyResult<Uuid> {
//! Uuid::parse_str(s).map_err(|e| PyValueError::new_err(e.to_string()))
//! }
//!
//! /// Passing a Python uuid.UUID directly to Rust.
//! #[pyfunction]
//! fn get_uuid(u: Uuid) -> Uuid {
//! u
//! }
//!
//! #[pymodule]
//! fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
//! m.add_function(wrap_pyfunction!(get_uuid_from_str, m)?)?;
//! m.add_function(wrap_pyfunction!(get_uuid, m)?)?;
//! Ok(())
//! }
//! ```
//!
//! Python code that validates the functionality
//!
//!
//! ```python
//! from my_module import get_uuid_from_str, get_uuid
//! import uuid
//!
//! py_uuid = uuid.uuid4()
//!
//! # Convert string to Rust Uuid
//! rust_uuid = get_uuid_from_str(str(py_uuid))
//! assert py_uuid == rust_uuid
//!
//! # Pass Python UUID directly to Rust
//! returned_uuid = get_uuid(py_uuid)
//! assert py_uuid == returned_uuid
//! ```
use uuid::{NonNilUuid, Uuid};
use crate::conversion::IntoPyObject;
use crate::exceptions::{PyTypeError, PyValueError};
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
use crate::instance::Bound;
use crate::sync::PyOnceLock;
#[cfg(feature = "experimental-inspect")]
use crate::type_hint_identifier;
use crate::types::any::PyAnyMethods;
use crate::types::PyType;
use crate::{intern, Borrowed, FromPyObject, Py, PyAny, PyErr, PyResult, Python};
fn get_uuid_cls(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
static UUID_CLS: PyOnceLock<Py<PyType>> = PyOnceLock::new();
UUID_CLS.import(py, "uuid", "UUID")
}
impl FromPyObject<'_, '_> for Uuid {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = type_hint_identifier!("uuid", "UUID");
fn extract(obj: Borrowed<'_, '_, PyAny>) -> PyResult<Self> {
let py = obj.py();
let uuid_cls = get_uuid_cls(py)?;
if obj.is_instance(uuid_cls)? {
let uuid_int: u128 = obj.getattr(intern!(py, "int"))?.extract()?;
Ok(Uuid::from_u128(uuid_int))
} else {
Err(PyTypeError::new_err("Expected a `uuid.UUID` instance."))
}
}
}
impl<'py> IntoPyObject<'py> for Uuid {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("uuid", "UUID");
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let uuid_cls = get_uuid_cls(py)?;
uuid_cls.call1((py.None(), py.None(), py.None(), py.None(), self.as_u128()))
}
}
impl<'py> IntoPyObject<'py> for &Uuid {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = Uuid::OUTPUT_TYPE;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
impl FromPyObject<'_, '_> for NonNilUuid {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = Uuid::INPUT_TYPE;
fn extract(obj: Borrowed<'_, '_, PyAny>) -> PyResult<Self> {
let uuid: Uuid = obj.extract()?;
NonNilUuid::new(uuid).ok_or_else(|| PyValueError::new_err("UUID is nil"))
}
}
impl<'py> IntoPyObject<'py> for NonNilUuid {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = Uuid::OUTPUT_TYPE;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Uuid::from(self).into_pyobject(py)
}
}
impl<'py> IntoPyObject<'py> for &NonNilUuid {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = NonNilUuid::OUTPUT_TYPE;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::dict::PyDictMethods;
use crate::types::PyDict;
use alloc::ffi::CString;
use uuid::Uuid;
macro_rules! convert_constants {
($name:ident, $rs:expr, $py:literal) => {
#[test]
fn $name() -> PyResult<()> {
Python::attach(|py| {
let rs_orig = $rs;
let rs_uuid = rs_orig.into_pyobject(py).unwrap();
let locals = PyDict::new(py);
locals.set_item("rs_uuid", &rs_uuid).unwrap();
py.run(
&CString::new(format!(
"import uuid\npy_uuid = uuid.UUID('{}')\nassert py_uuid == rs_uuid",
$py
))
.unwrap(),
None,
Some(&locals),
)
.unwrap();
let py_uuid = locals.get_item("py_uuid").unwrap().unwrap();
let py_result: Uuid = py_uuid.extract().unwrap();
assert_eq!(rs_orig, py_result);
Ok(())
})
}
};
}
convert_constants!(
convert_nil,
Uuid::nil(),
"00000000-0000-0000-0000-000000000000"
);
convert_constants!(
convert_max,
Uuid::max(),
"ffffffff-ffff-ffff-ffff-ffffffffffff"
);
convert_constants!(
convert_uuid_v4,
Uuid::parse_str("a4f6d1b9-1898-418f-b11d-ecc6fe1e1f00").unwrap(),
"a4f6d1b9-1898-418f-b11d-ecc6fe1e1f00"
);
convert_constants!(
convert_uuid_v3,
Uuid::parse_str("6fa459ea-ee8a-3ca4-894e-db77e160355e").unwrap(),
"6fa459ea-ee8a-3ca4-894e-db77e160355e"
);
convert_constants!(
convert_uuid_v1,
Uuid::parse_str("a6cc5730-2261-11ee-9c43-2eb5a363657c").unwrap(),
"a6cc5730-2261-11ee-9c43-2eb5a363657c"
);
#[test]
fn test_non_nil_uuid() {
Python::attach(|py| {
let rs_uuid = NonNilUuid::new(Uuid::max()).unwrap();
let py_uuid = rs_uuid.into_pyobject(py).unwrap();
let extract_uuid: NonNilUuid = py_uuid.extract().unwrap();
assert_eq!(extract_uuid, rs_uuid);
let nil_uuid = Uuid::nil().into_pyobject(py).unwrap();
let extract_nil: PyResult<NonNilUuid> = nil_uuid.extract();
assert!(extract_nil.is_err());
})
}
}
+163
View File
@@ -0,0 +1,163 @@
//! Python coroutine implementation, used notably when wrapping `async fn`
//! with `#[pyfunction]`/`#[pymethods]`.
use alloc::sync::Arc;
use core::{
future::Future,
panic,
pin::Pin,
task::{Context, Poll, Waker},
};
use pyo3_macros::{pyclass, pymethods};
use crate::{
coroutine::{cancel::ThrowCallback, waker::AsyncioWaker},
exceptions::{PyAttributeError, PyRuntimeError, PyStopIteration},
panic::PanicException,
types::{string::PyStringMethods, PyIterator, PyString},
Bound, Py, PyAny, PyErr, PyResult, Python,
};
pub(crate) mod cancel;
mod waker;
pub use cancel::CancelHandle;
const COROUTINE_REUSED_ERROR: &str = "cannot reuse already awaited coroutine";
/// Python coroutine wrapping a [`Future`].
#[pyclass(crate = "crate")]
pub struct Coroutine {
name: Option<Py<PyString>>,
qualname_prefix: Option<&'static str>,
throw_callback: Option<ThrowCallback>,
#[expect(clippy::type_complexity)]
future: Option<Pin<Box<dyn Future<Output = PyResult<Py<PyAny>>> + Send>>>,
waker: Option<Arc<AsyncioWaker>>,
}
// Safety: `Coroutine` is allowed to be `Sync` even though the future is not,
// because the future is polled with `&mut self` receiver
unsafe impl Sync for Coroutine {}
impl Coroutine {
/// Wrap a future into a Python coroutine.
///
/// Coroutine `send` polls the wrapped future, ignoring the value passed
/// (should always be `None` anyway).
///
/// `Coroutine `throw` drop the wrapped future and reraise the exception passed
pub(crate) fn new<'py, F>(
name: Option<Bound<'py, PyString>>,
qualname_prefix: Option<&'static str>,
throw_callback: Option<ThrowCallback>,
future: F,
) -> Self
where
F: Future<Output = Result<Py<PyAny>, PyErr>> + Send + 'static,
{
Self {
name: name.map(Bound::unbind),
qualname_prefix,
throw_callback,
future: Some(Box::pin(future)),
waker: None,
}
}
fn poll(&mut self, py: Python<'_>, throw: Option<Py<PyAny>>) -> PyResult<Py<PyAny>> {
// raise if the coroutine has already been run to completion
let future_rs = match self.future {
Some(ref mut fut) => fut,
None => return Err(PyRuntimeError::new_err(COROUTINE_REUSED_ERROR)),
};
// reraise thrown exception it
match (throw, &self.throw_callback) {
(Some(exc), Some(cb)) => cb.throw(exc),
(Some(exc), None) => {
self.close();
return Err(PyErr::from_value(exc.into_bound(py)));
}
(None, _) => {}
}
// create a new waker, or try to reset it in place
if let Some(waker) = self.waker.as_mut().and_then(Arc::get_mut) {
waker.reset();
} else {
self.waker = Some(Arc::new(AsyncioWaker::new()));
}
let waker = Waker::from(self.waker.clone().unwrap());
// poll the Rust future and forward its results if ready
// polling is UnwindSafe because the future is dropped in case of panic
let poll = || future_rs.as_mut().poll(&mut Context::from_waker(&waker));
match std::panic::catch_unwind(panic::AssertUnwindSafe(poll)) {
Ok(Poll::Ready(res)) => {
self.close();
return Err(PyStopIteration::new_err((res?,)));
}
Err(err) => {
self.close();
return Err(PanicException::from_panic_payload(err));
}
_ => {}
}
// otherwise, initialize the waker `asyncio.Future`
if let Some(future) = self.waker.as_ref().unwrap().initialize_future(py)? {
// `asyncio.Future` must be awaited; fortunately, it implements `__iter__ = __await__`
// and will yield itself if its result has not been set in polling above
if let Some(future) = PyIterator::from_object(future).unwrap().next() {
// future has not been leaked into Python for now, and Rust code can only call
// `set_result(None)` in `Wake` implementation, so it's safe to unwrap
return Ok(future.unwrap().into());
}
}
// if waker has been waken during future polling, this is roughly equivalent to
// `await asyncio.sleep(0)`, so just yield `None`.
Ok(py.None())
}
}
#[pymethods(crate = "crate")]
impl Coroutine {
#[getter]
fn __name__(&self, py: Python<'_>) -> PyResult<Py<PyString>> {
match &self.name {
Some(name) => Ok(name.clone_ref(py)),
None => Err(PyAttributeError::new_err("__name__")),
}
}
#[getter]
fn __qualname__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
match (&self.name, &self.qualname_prefix) {
(Some(name), Some(prefix)) => Ok(PyString::new(
py,
&format!("{}.{}", prefix, name.bind(py).to_cow()?),
)),
(Some(name), None) => Ok(name.bind(py).clone()),
(None, _) => Err(PyAttributeError::new_err("__qualname__")),
}
}
fn send(&mut self, py: Python<'_>, _value: &Bound<'_, PyAny>) -> PyResult<Py<PyAny>> {
self.poll(py, None)
}
fn throw(&mut self, py: Python<'_>, exc: Py<PyAny>) -> PyResult<Py<PyAny>> {
self.poll(py, Some(exc))
}
fn close(&mut self) {
// the Rust future is dropped, and the field set to `None`
// to indicate the coroutine has been run to completion
drop(self.future.take());
}
fn __await__(self_: Py<Self>) -> Py<Self> {
self_
}
fn __next__(&mut self, py: Python<'_>) -> PyResult<Py<PyAny>> {
self.poll(py, None)
}
}
+67
View File
@@ -0,0 +1,67 @@
use crate::{Py, PyAny};
use alloc::sync::Arc;
use core::future::poll_fn;
use core::task::{Context, Poll, Waker};
use std::sync::Mutex;
#[derive(Debug, Default)]
struct Inner {
exception: Option<Py<PyAny>>,
waker: Option<Waker>,
}
/// Helper used to wait and retrieve exception thrown in [`Coroutine`](super::Coroutine).
///
/// Only the last exception thrown can be retrieved.
#[derive(Debug, Default)]
pub struct CancelHandle(Arc<Mutex<Inner>>);
impl CancelHandle {
/// Create a new `CoroutineCancel`.
pub fn new() -> Self {
Default::default()
}
/// Returns whether the associated coroutine has been cancelled.
pub fn is_cancelled(&self) -> bool {
self.0.lock().unwrap().exception.is_some()
}
/// Poll to retrieve the exception thrown in the associated coroutine.
pub fn poll_cancelled(&mut self, cx: &mut Context<'_>) -> Poll<Py<PyAny>> {
let mut inner = self.0.lock().unwrap();
if let Some(exc) = inner.exception.take() {
return Poll::Ready(exc);
}
if let Some(ref waker) = inner.waker {
if cx.waker().will_wake(waker) {
return Poll::Pending;
}
}
inner.waker = Some(cx.waker().clone());
Poll::Pending
}
/// Retrieve the exception thrown in the associated coroutine.
pub async fn cancelled(&mut self) -> Py<PyAny> {
poll_fn(|cx| self.poll_cancelled(cx)).await
}
#[doc(hidden)]
pub fn throw_callback(&self) -> ThrowCallback {
ThrowCallback(self.0.clone())
}
}
#[doc(hidden)]
pub struct ThrowCallback(Arc<Mutex<Inner>>);
impl ThrowCallback {
pub(super) fn throw(&self, exc: Py<PyAny>) {
let mut inner = self.0.lock().unwrap();
inner.exception = Some(exc);
if let Some(waker) = inner.waker.take() {
waker.wake();
}
}
}
+106
View File
@@ -0,0 +1,106 @@
use crate::sync::PyOnceLock;
use crate::types::any::PyAnyMethods;
use crate::types::PyCFunction;
use crate::{intern, wrap_pyfunction, Bound, Py, PyAny, PyResult, Python};
use alloc::sync::Arc;
use alloc::task::Wake;
use pyo3_macros::pyfunction;
/// Lazy `asyncio.Future` wrapper, implementing [`Wake`] by calling `Future.set_result`.
///
/// asyncio future is let uninitialized until [`initialize_future`][1] is called.
/// If [`wake`][2] is called before future initialization (during Rust future polling),
/// [`initialize_future`][1] will return `None` (it is roughly equivalent to `asyncio.sleep(0)`)
///
/// [1]: AsyncioWaker::initialize_future
/// [2]: AsyncioWaker::wake
pub struct AsyncioWaker(PyOnceLock<Option<LoopAndFuture>>);
impl AsyncioWaker {
pub(super) fn new() -> Self {
Self(PyOnceLock::new())
}
pub(super) fn reset(&mut self) {
self.0.take();
}
pub(super) fn initialize_future<'py>(
&self,
py: Python<'py>,
) -> PyResult<Option<&Bound<'py, PyAny>>> {
let init = || LoopAndFuture::new(py).map(Some);
let loop_and_future = self.0.get_or_try_init(py, init)?.as_ref();
Ok(loop_and_future.map(|LoopAndFuture { future, .. }| future.bind(py)))
}
}
impl Wake for AsyncioWaker {
fn wake(self: Arc<Self>) {
self.wake_by_ref()
}
fn wake_by_ref(self: &Arc<Self>) {
Python::attach(|py| {
if let Some(loop_and_future) = self.0.get_or_init(py, || None) {
loop_and_future
.set_result(py)
.expect("unexpected error in coroutine waker");
}
});
}
}
struct LoopAndFuture {
event_loop: Py<PyAny>,
future: Py<PyAny>,
}
impl LoopAndFuture {
fn new(py: Python<'_>) -> PyResult<Self> {
static GET_RUNNING_LOOP: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
let import = || -> PyResult<_> {
let module = py.import("asyncio")?;
Ok(module.getattr("get_running_loop")?.into())
};
let event_loop = GET_RUNNING_LOOP.get_or_try_init(py, import)?.call0(py)?;
let future = event_loop.call_method0(py, "create_future")?;
Ok(Self { event_loop, future })
}
fn set_result(&self, py: Python<'_>) -> PyResult<()> {
static RELEASE_WAITER: PyOnceLock<Py<PyCFunction>> = PyOnceLock::new();
let release_waiter = RELEASE_WAITER.get_or_try_init(py, || {
wrap_pyfunction!(release_waiter, py).map(Bound::unbind)
})?;
// `Future.set_result` must be called in event loop thread,
// so it requires `call_soon_threadsafe`
let call_soon_threadsafe = self.event_loop.call_method1(
py,
intern!(py, "call_soon_threadsafe"),
(release_waiter, self.future.bind(py)),
);
if let Err(err) = call_soon_threadsafe {
// `call_soon_threadsafe` will raise if the event loop is closed;
// instead of catching an unspecific `RuntimeError`, check directly if it's closed.
let is_closed = self.event_loop.call_method0(py, "is_closed")?;
if !is_closed.extract(py)? {
return Err(err);
}
}
Ok(())
}
}
/// Call `future.set_result` if the future is not done.
///
/// Future can be cancelled by the event loop before being waken.
/// See <https://github.com/python/cpython/blob/main/Lib/asyncio/tasks.py#L452C5-L452C5>
#[pyfunction(crate = "crate")]
fn release_waiter(future: &Bound<'_, PyAny>) -> PyResult<()> {
let done = future.call_method0(intern!(future.py(), "done"))?;
if !done.extract::<bool>()? {
future.call_method1(intern!(future.py(), "set_result"), (future.py().None(),))?;
}
Ok(())
}
+265
View File
@@ -0,0 +1,265 @@
use alloc::borrow::Cow;
use crate::{
exceptions,
types::{
PyAnyMethods, PyNone, PyStringMethods, PyTuple, PyTupleMethods, PyType, PyTypeMethods,
},
Borrowed, Bound, IntoPyObjectExt, Py, PyAny, PyErr, PyErrArguments, PyTypeInfo, Python,
};
/// Error that indicates an object was not an instance of a given target type.
#[derive(Debug)]
pub struct CastError<'a, 'py> {
/// The original object that failed the `isinstance` check.
from: Borrowed<'a, 'py, PyAny>,
/// The type we tried (and failed) to convert to.
/// (see `PyTypeCheck::classinfo_object`)
classinfo: Bound<'py, PyAny>,
}
impl<'a, 'py> CastError<'a, 'py> {
/// Create a new `CastError` representing a failure to interpret a smart pointer to
/// `from` as a type from the given `classinfo`.
///
/// As with [`PyTypeCheck::classinfo_object`][crate::PyTypeCheck::classinfo_object],
/// valid `classinfo` values are those which can be used with `isinstance`, such as `type`
/// objects, tuples of `type` objects, or `typing.Union` instances.
#[inline]
pub fn new(from: Borrowed<'a, 'py, PyAny>, classinfo: Bound<'py, PyAny>) -> Self {
Self { from, classinfo }
}
}
/// Equivalent to [`CastError`] for operations where the smart pointer cast transfers ownership
/// of the original object.
///
/// The original object can be retrieved using [`into_inner`][CastIntoError::into_inner].
#[derive(Debug)]
pub struct CastIntoError<'py> {
from: Bound<'py, PyAny>,
classinfo: Bound<'py, PyAny>,
}
impl<'py> CastIntoError<'py> {
/// Create a new `CastError` representing a failure to interpret a smart pointer to
/// `from` as a type from the given `classinfo`.
///
/// Equivalent to [`CastError::new`] for owned objects.
#[inline]
pub fn new(from: Bound<'py, PyAny>, classinfo: Bound<'py, PyAny>) -> Self {
Self { from, classinfo }
}
/// Consumes this `CastIntoError` and returns the original object, allowing continued
/// use of it after a failed conversion.
///
/// See [`cast_into`][Bound::cast_into] for an example.
pub fn into_inner(self) -> Bound<'py, PyAny> {
self.from
}
}
struct CastErrorArguments {
from: Py<PyAny>,
classinfo: Py<PyAny>,
}
impl PyErrArguments for CastErrorArguments {
fn arguments(self, py: Python<'_>) -> Py<PyAny> {
format!(
"{}",
DisplayCastError {
from: &self.from.into_bound(py),
classinfo: &self.classinfo.into_bound(py),
}
)
.into_py_any(py)
.expect("failed to create Python string")
}
}
/// Convert `CastError` to Python `TypeError`.
impl core::convert::From<CastError<'_, '_>> for PyErr {
fn from(err: CastError<'_, '_>) -> PyErr {
let args = CastErrorArguments {
from: err.from.to_owned().unbind(),
classinfo: err.classinfo.unbind(),
};
exceptions::PyTypeError::new_err(args)
}
}
impl core::error::Error for CastError<'_, '_> {}
impl core::fmt::Display for CastError<'_, '_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
DisplayCastError {
from: &self.from,
classinfo: &self.classinfo,
}
.fmt(f)
}
}
/// Convert `CastIntoError` to Python `TypeError`.
impl core::convert::From<CastIntoError<'_>> for PyErr {
fn from(err: CastIntoError<'_>) -> PyErr {
let args = CastErrorArguments {
from: err.from.to_owned().unbind(),
classinfo: err.classinfo.unbind(),
};
exceptions::PyTypeError::new_err(args)
}
}
impl core::error::Error for CastIntoError<'_> {}
impl core::fmt::Display for CastIntoError<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
DisplayCastError {
from: &self.from.to_owned(),
classinfo: &self.classinfo,
}
.fmt(f)
}
}
struct DisplayCastError<'a, 'py> {
from: &'a Bound<'py, PyAny>,
classinfo: &'a Bound<'py, PyAny>,
}
impl core::fmt::Display for DisplayCastError<'_, '_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let to = DisplayClassInfo(self.classinfo);
if self.from.is_none() {
write!(f, "'None' is not an instance of '{to}'")
} else {
let from = self.from.get_type().qualname();
let from = from
.as_ref()
.map(|name| name.to_string_lossy())
.unwrap_or(Cow::Borrowed("<failed to extract type name>"));
write!(f, "'{from}' object is not an instance of '{to}'")
}
}
}
struct DisplayClassInfo<'a, 'py>(&'a Bound<'py, PyAny>);
impl core::fmt::Display for DisplayClassInfo<'_, '_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if let Ok(t) = self.0.cast::<PyType>() {
if t.is(PyNone::type_object(t.py())) {
f.write_str("None")
} else {
t.qualname()
.map_err(|_| core::fmt::Error)?
.to_string_lossy()
.fmt(f)
}
} else if let Ok(t) = self.0.cast::<PyTuple>() {
for (i, t) in t.iter().enumerate() {
if i > 0 {
f.write_str(" | ")?;
}
write!(f, "{}", DisplayClassInfo(&t))?;
}
Ok(())
} else {
self.0.fmt(f)
}
}
}
#[cfg(test)]
mod tests {
use crate::{
types::{PyBool, PyString},
PyTypeInfo,
};
use super::*;
#[test]
fn test_display_cast_error() {
Python::attach(|py| {
let obj = PyBool::new(py, true).to_any();
let classinfo = py.get_type::<PyString>().into_any();
let err = CastError::new(obj, classinfo);
assert_eq!(err.to_string(), "'bool' object is not an instance of 'str'");
})
}
#[test]
fn test_display_cast_error_with_none() {
Python::attach(|py| {
let obj = py.None().into_bound(py);
let classinfo = py.get_type::<PyString>().into_any();
let err = CastError::new(obj.as_borrowed(), classinfo);
assert_eq!(err.to_string(), "'None' is not an instance of 'str'");
})
}
#[test]
fn test_display_cast_error_with_tuple() {
Python::attach(|py| {
let obj = PyBool::new(py, true).to_any();
let classinfo = PyTuple::new(
py,
&[
py.get_type::<PyString>().into_any(),
crate::types::PyNone::type_object(py).into_any(),
],
)
.unwrap()
.into_any();
let err = CastError::new(obj, classinfo);
assert_eq!(
err.to_string(),
"'bool' object is not an instance of 'str | None'"
);
})
}
#[test]
fn test_display_cast_into_error() {
Python::attach(|py| {
let obj = PyBool::new(py, true).to_any();
let classinfo = py.get_type::<PyString>().into_any();
let err = CastIntoError::new(obj.to_owned(), classinfo);
assert_eq!(err.to_string(), "'bool' object is not an instance of 'str'");
})
}
#[test]
fn test_pyerr_from_cast_error() {
Python::attach(|py| {
let obj = PyBool::new(py, true).to_any();
let classinfo = py.get_type::<PyString>().into_any();
let err = CastError::new(obj, classinfo);
let py_err: PyErr = err.into();
assert_eq!(
py_err.to_string(),
"TypeError: 'bool' object is not an instance of 'str'"
);
})
}
#[test]
fn test_pyerr_from_cast_into_error() {
Python::attach(|py| {
let obj = PyBool::new(py, true).to_any();
let classinfo = py.get_type::<PyString>().into_any();
let err = CastIntoError::new(obj.to_owned(), classinfo);
let py_err: PyErr = err.into();
assert_eq!(
py_err.to_string(),
"TypeError: 'bool' object is not an instance of 'str'"
);
})
}
}
+484
View File
@@ -0,0 +1,484 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
use core::cell::UnsafeCell;
use std::{
sync::{Mutex, Once},
thread::ThreadId,
};
#[cfg(not(Py_3_12))]
use crate::sync::MutexExt;
use crate::{
exceptions::{PyBaseException, PyTypeError},
ffi,
ffi_ptr_ext::FfiPtrExt,
types::{PyAnyMethods, PyTraceback, PyType},
Bound, Py, PyAny, PyErrArguments, PyTypeInfo, Python,
};
pub(crate) struct PyErrState {
// Safety: can only hand out references when in the "normalized" state. Will never change
// after normalization.
normalized: Once,
// Guard against re-entrancy when normalizing the exception state.
normalizing_thread: Mutex<Option<ThreadId>>,
inner: UnsafeCell<Option<PyErrStateInner>>,
}
// Safety: The inner value is protected by locking to ensure that only the normalized state is
// handed out as a reference.
unsafe impl Send for PyErrState {}
unsafe impl Sync for PyErrState {}
#[cfg(feature = "nightly")]
unsafe impl crate::marker::Ungil for PyErrState {}
impl PyErrState {
pub(crate) fn lazy(f: Box<PyErrStateLazyFn>) -> Self {
Self::from_inner(PyErrStateInner::Lazy(f))
}
pub(crate) fn lazy_arguments(ptype: Py<PyAny>, args: impl PyErrArguments + 'static) -> Self {
Self::from_inner(PyErrStateInner::Lazy(Box::new(move |py| {
PyErrStateLazyFnOutput {
ptype,
pvalue: args.arguments(py),
}
})))
}
pub(crate) fn normalized(normalized: PyErrStateNormalized) -> Self {
let state = Self::from_inner(PyErrStateInner::Normalized(normalized));
// This state is already normalized, by completing the Once immediately we avoid
// reaching the `py.detach` in `make_normalized` which is less efficient
// and introduces a GIL switch which could deadlock.
// See https://github.com/PyO3/pyo3/issues/4764
state.normalized.call_once(|| {});
state
}
pub(crate) fn restore(self, py: Python<'_>) {
self.inner
.into_inner()
.expect("PyErr state should never be invalid outside of normalization")
.restore(py)
}
fn from_inner(inner: PyErrStateInner) -> Self {
Self {
normalized: Once::new(),
normalizing_thread: Mutex::new(None),
inner: UnsafeCell::new(Some(inner)),
}
}
#[inline]
pub(crate) fn as_normalized(&self, py: Python<'_>) -> &PyErrStateNormalized {
if self.normalized.is_completed() {
match unsafe {
// Safety: self.inner will never be written again once normalized.
&*self.inner.get()
} {
Some(PyErrStateInner::Normalized(n)) => return n,
_ => unreachable!(),
}
}
self.make_normalized(py)
}
#[cold]
fn make_normalized(&self, py: Python<'_>) -> &PyErrStateNormalized {
// This process is safe because:
// - Write happens only once, and then never will change again.
// - The `Once` ensure that only one thread will do the write.
// Guard against re-entrant normalization, because `Once` does not provide
// re-entrancy guarantees.
if let Some(thread) = self.normalizing_thread.lock().unwrap().as_ref() {
assert!(
!(*thread == std::thread::current().id()),
"Re-entrant normalization of PyErrState detected"
);
}
// avoid deadlock of `.call_once` with the GIL
py.detach(|| {
self.normalized.call_once(|| {
self.normalizing_thread
.lock()
.unwrap()
.replace(std::thread::current().id());
// Safety: no other thread can access the inner value while we are normalizing it.
let state = unsafe {
(*self.inner.get())
.take()
.expect("Cannot normalize a PyErr while already normalizing it.")
};
let normalized_state =
Python::attach(|py| PyErrStateInner::Normalized(state.normalize(py)));
// Safety: no other thread can access the inner value while we are normalizing it.
unsafe {
*self.inner.get() = Some(normalized_state);
}
})
});
match unsafe {
// Safety: self.inner will never be written again once normalized.
&*self.inner.get()
} {
Some(PyErrStateInner::Normalized(n)) => n,
_ => unreachable!(),
}
}
}
pub(crate) struct PyErrStateNormalized {
#[cfg(not(Py_3_12))]
ptype: Py<PyType>,
pub pvalue: Py<PyBaseException>,
#[cfg(not(Py_3_12))]
ptraceback: std::sync::Mutex<Option<Py<PyTraceback>>>,
}
impl PyErrStateNormalized {
pub(crate) fn new(pvalue: Bound<'_, PyBaseException>) -> Self {
Self {
#[cfg(not(Py_3_12))]
ptype: pvalue.get_type().into(),
#[cfg(not(Py_3_12))]
ptraceback: unsafe {
Mutex::new(
ffi::PyException_GetTraceback(pvalue.as_ptr())
.assume_owned_or_opt(pvalue.py())
.map(|b| b.cast_into_unchecked().unbind()),
)
},
pvalue: pvalue.into(),
}
}
#[cfg(not(Py_3_12))]
pub(crate) fn ptype<'py>(&self, py: Python<'py>) -> Bound<'py, PyType> {
self.ptype.bind(py).clone()
}
#[cfg(Py_3_12)]
pub(crate) fn ptype<'py>(&self, py: Python<'py>) -> Bound<'py, PyType> {
self.pvalue.bind(py).get_type()
}
#[cfg(not(Py_3_12))]
pub(crate) fn ptraceback<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyTraceback>> {
self.ptraceback
.lock_py_attached(py)
.unwrap()
.as_ref()
.map(|traceback| traceback.bind(py).clone())
}
#[cfg(Py_3_12)]
pub(crate) fn ptraceback<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyTraceback>> {
unsafe {
ffi::PyException_GetTraceback(self.pvalue.as_ptr())
.assume_owned_or_opt(py)
.map(|b| b.cast_into_unchecked())
}
}
#[cfg(not(Py_3_12))]
pub(crate) fn set_ptraceback<'py>(&self, py: Python<'py>, tb: Option<Bound<'py, PyTraceback>>) {
*self.ptraceback.lock_py_attached(py).unwrap() = tb.map(Bound::unbind);
}
#[cfg(Py_3_12)]
pub(crate) fn set_ptraceback<'py>(&self, py: Python<'py>, tb: Option<Bound<'py, PyTraceback>>) {
let tb = tb
.as_ref()
.map(Bound::as_ptr)
.unwrap_or_else(|| crate::types::PyNone::get(py).as_ptr());
unsafe { ffi::PyException_SetTraceback(self.pvalue.as_ptr(), tb) };
}
pub(crate) fn take(py: Python<'_>) -> Option<PyErrStateNormalized> {
#[cfg(Py_3_12)]
{
// Safety: PyErr_GetRaisedException can be called when attached to Python and
// returns either NULL or an owned reference.
unsafe { ffi::PyErr_GetRaisedException().assume_owned_or_opt(py) }.map(|pvalue| {
PyErrStateNormalized {
// Safety: PyErr_GetRaisedException returns a valid exception type.
pvalue: unsafe { pvalue.cast_into_unchecked() }.unbind(),
}
})
}
#[cfg(not(Py_3_12))]
{
let (ptype, pvalue, ptraceback) = unsafe {
let mut ptype: *mut ffi::PyObject = core::ptr::null_mut();
let mut pvalue: *mut ffi::PyObject = core::ptr::null_mut();
let mut ptraceback: *mut ffi::PyObject = core::ptr::null_mut();
ffi::PyErr_Fetch(&mut ptype, &mut pvalue, &mut ptraceback);
// Ensure that the exception coming from the interpreter is normalized.
if !ptype.is_null() {
ffi::PyErr_NormalizeException(&mut ptype, &mut pvalue, &mut ptraceback);
}
// Safety: PyErr_NormalizeException will have produced up to three owned
// references of the correct types.
(
ptype
.assume_owned_or_opt(py)
.map(|b| b.cast_into_unchecked()),
pvalue
.assume_owned_or_opt(py)
.map(|b| b.cast_into_unchecked()),
ptraceback
.assume_owned_or_opt(py)
.map(|b| b.cast_into_unchecked()),
)
};
ptype.map(|ptype| PyErrStateNormalized {
ptype: ptype.unbind(),
pvalue: pvalue.expect("normalized exception value missing").unbind(),
ptraceback: std::sync::Mutex::new(ptraceback.map(Bound::unbind)),
})
}
}
#[cfg(not(Py_3_12))]
unsafe fn from_normalized_ffi_tuple(
py: Python<'_>,
ptype: *mut ffi::PyObject,
pvalue: *mut ffi::PyObject,
ptraceback: *mut ffi::PyObject,
) -> Self {
PyErrStateNormalized {
ptype: unsafe {
ptype
.assume_owned_or_opt(py)
.expect("Exception type missing")
.cast_into_unchecked()
}
.unbind(),
pvalue: unsafe {
pvalue
.assume_owned_or_opt(py)
.expect("Exception value missing")
.cast_into_unchecked()
}
.unbind(),
ptraceback: Mutex::new(
unsafe { ptraceback.assume_owned_or_opt(py) }
.map(|b| unsafe { b.cast_into_unchecked() }.unbind()),
),
}
}
pub fn clone_ref(&self, py: Python<'_>) -> Self {
Self {
#[cfg(not(Py_3_12))]
ptype: self.ptype.clone_ref(py),
pvalue: self.pvalue.clone_ref(py),
#[cfg(not(Py_3_12))]
ptraceback: std::sync::Mutex::new(
self.ptraceback
.lock_py_attached(py)
.unwrap()
.as_ref()
.map(|ptraceback| ptraceback.clone_ref(py)),
),
}
}
}
pub(crate) struct PyErrStateLazyFnOutput {
pub(crate) ptype: Py<PyAny>,
pub(crate) pvalue: Py<PyAny>,
}
pub(crate) type PyErrStateLazyFn =
dyn for<'py> FnOnce(Python<'py>) -> PyErrStateLazyFnOutput + Send + Sync;
enum PyErrStateInner {
Lazy(Box<PyErrStateLazyFn>),
Normalized(PyErrStateNormalized),
}
impl PyErrStateInner {
fn normalize(self, py: Python<'_>) -> PyErrStateNormalized {
match self {
#[cfg(not(Py_3_12))]
PyErrStateInner::Lazy(lazy) => {
let (ptype, pvalue, ptraceback) = lazy_into_normalized_ffi_tuple(py, lazy);
unsafe {
PyErrStateNormalized::from_normalized_ffi_tuple(py, ptype, pvalue, ptraceback)
}
}
#[cfg(Py_3_12)]
PyErrStateInner::Lazy(lazy) => {
// To keep the implementation simple, just write the exception into the interpreter,
// which will cause it to be normalized
raise_lazy(py, lazy);
PyErrStateNormalized::take(py)
.expect("exception missing after writing to the interpreter")
}
PyErrStateInner::Normalized(normalized) => normalized,
}
}
#[cfg(not(Py_3_12))]
fn restore(self, py: Python<'_>) {
let (ptype, pvalue, ptraceback) = match self {
PyErrStateInner::Lazy(lazy) => lazy_into_normalized_ffi_tuple(py, lazy),
PyErrStateInner::Normalized(PyErrStateNormalized {
ptype,
pvalue,
ptraceback,
}) => (
ptype.into_ptr(),
pvalue.into_ptr(),
ptraceback
.into_inner()
.unwrap()
.map_or(core::ptr::null_mut(), Py::into_ptr),
),
};
unsafe { ffi::PyErr_Restore(ptype, pvalue, ptraceback) }
}
#[cfg(Py_3_12)]
fn restore(self, py: Python<'_>) {
match self {
PyErrStateInner::Lazy(lazy) => raise_lazy(py, lazy),
PyErrStateInner::Normalized(PyErrStateNormalized { pvalue }) => unsafe {
ffi::PyErr_SetRaisedException(pvalue.into_ptr())
},
}
}
}
#[cfg(not(Py_3_12))]
fn lazy_into_normalized_ffi_tuple(
py: Python<'_>,
lazy: Box<PyErrStateLazyFn>,
) -> (*mut ffi::PyObject, *mut ffi::PyObject, *mut ffi::PyObject) {
// To be consistent with 3.12 logic, go via raise_lazy, but also then normalize
// the resulting exception
raise_lazy(py, lazy);
let mut ptype = core::ptr::null_mut();
let mut pvalue = core::ptr::null_mut();
let mut ptraceback = core::ptr::null_mut();
unsafe {
ffi::PyErr_Fetch(&mut ptype, &mut pvalue, &mut ptraceback);
ffi::PyErr_NormalizeException(&mut ptype, &mut pvalue, &mut ptraceback);
}
(ptype, pvalue, ptraceback)
}
/// Raises a "lazy" exception state into the Python interpreter.
///
/// In principle this could be split in two; first a function to create an exception
/// in a normalized state, and then a call to `PyErr_SetRaisedException` to raise it.
///
/// This would require either moving some logic from C to Rust, or requesting a new
/// API in CPython.
fn raise_lazy(py: Python<'_>, lazy: Box<PyErrStateLazyFn>) {
let PyErrStateLazyFnOutput { ptype, pvalue } = lazy(py);
unsafe {
if ffi::PyExceptionClass_Check(ptype.as_ptr()) == 0 {
ffi::PyErr_SetString(
PyTypeError::type_object_raw(py).cast(),
c"exceptions must derive from BaseException".as_ptr(),
)
} else {
ffi::PyErr_SetObject(ptype.as_ptr(), pvalue.as_ptr())
}
}
}
#[cfg(test)]
mod tests {
use crate::{
exceptions::PyValueError, sync::PyOnceLock, Py, PyAny, PyErr, PyErrArguments, Python,
};
#[test]
#[should_panic(expected = "Re-entrant normalization of PyErrState detected")]
fn test_reentrant_normalization() {
static ERR: PyOnceLock<PyErr> = PyOnceLock::new();
struct RecursiveArgs;
impl PyErrArguments for RecursiveArgs {
fn arguments(self, py: Python<'_>) -> Py<PyAny> {
// .value(py) triggers normalization
ERR.get(py)
.expect("is set just below")
.value(py)
.clone()
.into()
}
}
Python::attach(|py| {
ERR.set(py, PyValueError::new_err(RecursiveArgs)).unwrap();
ERR.get(py).expect("is set just above").value(py);
})
}
#[test]
#[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
fn test_no_deadlock_thread_switch() {
static ERR: PyOnceLock<PyErr> = PyOnceLock::new();
struct GILSwitchArgs;
impl PyErrArguments for GILSwitchArgs {
fn arguments(self, py: Python<'_>) -> Py<PyAny> {
// releasing the GIL potentially allows for other threads to deadlock
// with the normalization going on here
py.detach(|| {
std::thread::sleep(core::time::Duration::from_millis(10));
});
py.None()
}
}
Python::attach(|py| ERR.set(py, PyValueError::new_err(GILSwitchArgs)).unwrap());
// Let many threads attempt to read the normalized value at the same time
let handles = (0..10)
.map(|_| {
std::thread::spawn(|| {
Python::attach(|py| {
ERR.get(py).expect("is set just above").value(py);
});
})
})
.collect::<Vec<_>>();
for handle in handles {
handle.join().unwrap();
}
// We should never have deadlocked, and should be able to run
// this assertion
Python::attach(|py| {
assert!(ERR
.get(py)
.expect("is set above")
.is_instance_of::<PyValueError>(py))
});
}
}
+348
View File
@@ -0,0 +1,348 @@
use crate::{err::PyErrArguments, exceptions, types, PyErr, Python};
use crate::{IntoPyObject, Py, PyAny};
use std::io;
/// Convert `PyErr` to `io::Error`
impl From<PyErr> for io::Error {
fn from(err: PyErr) -> Self {
let kind = Python::attach(|py| {
if err.is_instance_of::<exceptions::PyBrokenPipeError>(py) {
io::ErrorKind::BrokenPipe
} else if err.is_instance_of::<exceptions::PyConnectionRefusedError>(py) {
io::ErrorKind::ConnectionRefused
} else if err.is_instance_of::<exceptions::PyConnectionAbortedError>(py) {
io::ErrorKind::ConnectionAborted
} else if err.is_instance_of::<exceptions::PyConnectionResetError>(py) {
io::ErrorKind::ConnectionReset
} else if err.is_instance_of::<exceptions::PyInterruptedError>(py) {
io::ErrorKind::Interrupted
} else if err.is_instance_of::<exceptions::PyFileNotFoundError>(py) {
io::ErrorKind::NotFound
} else if err.is_instance_of::<exceptions::PyPermissionError>(py) {
io::ErrorKind::PermissionDenied
} else if err.is_instance_of::<exceptions::PyFileExistsError>(py) {
io::ErrorKind::AlreadyExists
} else if err.is_instance_of::<exceptions::PyBlockingIOError>(py) {
io::ErrorKind::WouldBlock
} else if err.is_instance_of::<exceptions::PyTimeoutError>(py) {
io::ErrorKind::TimedOut
} else if err.is_instance_of::<exceptions::PyMemoryError>(py) {
io::ErrorKind::OutOfMemory
} else if err.is_instance_of::<exceptions::PyIsADirectoryError>(py) {
io::ErrorKind::IsADirectory
} else if err.is_instance_of::<exceptions::PyNotADirectoryError>(py) {
io::ErrorKind::NotADirectory
} else {
io::ErrorKind::Other
}
});
io::Error::new(kind, err)
}
}
/// Create `PyErr` from `io::Error`
/// (`OSError` except if the `io::Error` is wrapping a Python exception,
/// in this case the exception is returned)
impl From<io::Error> for PyErr {
fn from(err: io::Error) -> PyErr {
// If the error wraps a Python error we return it
if err.get_ref().is_some_and(|e| e.is::<PyErr>()) {
return *err.into_inner().unwrap().downcast().unwrap();
}
match err.kind() {
io::ErrorKind::BrokenPipe => exceptions::PyBrokenPipeError::new_err(err),
io::ErrorKind::ConnectionRefused => exceptions::PyConnectionRefusedError::new_err(err),
io::ErrorKind::ConnectionAborted => exceptions::PyConnectionAbortedError::new_err(err),
io::ErrorKind::ConnectionReset => exceptions::PyConnectionResetError::new_err(err),
io::ErrorKind::Interrupted => exceptions::PyInterruptedError::new_err(err),
io::ErrorKind::NotFound => exceptions::PyFileNotFoundError::new_err(err),
io::ErrorKind::PermissionDenied => exceptions::PyPermissionError::new_err(err),
io::ErrorKind::AlreadyExists => exceptions::PyFileExistsError::new_err(err),
io::ErrorKind::WouldBlock => exceptions::PyBlockingIOError::new_err(err),
io::ErrorKind::TimedOut => exceptions::PyTimeoutError::new_err(err),
io::ErrorKind::OutOfMemory => exceptions::PyMemoryError::new_err(err),
io::ErrorKind::IsADirectory => exceptions::PyIsADirectoryError::new_err(err),
io::ErrorKind::NotADirectory => exceptions::PyNotADirectoryError::new_err(err),
_ => exceptions::PyOSError::new_err(err),
}
}
}
impl PyErrArguments for io::Error {
fn arguments(self, py: Python<'_>) -> Py<PyAny> {
//FIXME(icxolu) remove unwrap
self.to_string()
.into_pyobject(py)
.unwrap()
.into_any()
.unbind()
}
}
impl<W> From<io::IntoInnerError<W>> for PyErr {
fn from(err: io::IntoInnerError<W>) -> PyErr {
err.into_error().into()
}
}
impl<W: Send + Sync> PyErrArguments for io::IntoInnerError<W> {
fn arguments(self, py: Python<'_>) -> Py<PyAny> {
self.into_error().arguments(py)
}
}
impl From<core::convert::Infallible> for PyErr {
fn from(_: core::convert::Infallible) -> PyErr {
unreachable!()
}
}
macro_rules! impl_to_pyerr {
($err: ty, $pyexc: ty) => {
impl PyErrArguments for $err {
fn arguments(self, py: Python<'_>) -> $crate::Py<$crate::PyAny> {
// FIXME(icxolu) remove unwrap
self.to_string()
.into_pyobject(py)
.unwrap()
.into_any()
.unbind()
}
}
impl core::convert::From<$err> for PyErr {
fn from(err: $err) -> PyErr {
<$pyexc>::new_err(err)
}
}
};
}
struct Utf8ErrorWithBytes {
err: core::str::Utf8Error,
bytes: Vec<u8>,
}
impl PyErrArguments for Utf8ErrorWithBytes {
fn arguments(self, py: Python<'_>) -> Py<PyAny> {
let Self { err, bytes } = self;
let start = err.valid_up_to();
let end = err.error_len().map_or(bytes.len(), |l| start + l);
let encoding = types::PyString::new(py, "utf-8").into_any();
let bytes = types::PyBytes::new(py, &bytes).into_any();
let start = types::PyInt::new(py, start).into_any();
let end = types::PyInt::new(py, end).into_any();
let reason = types::PyString::new(py, "invalid utf-8").into_any();
// FIXME(icxolu) remove unwrap
types::PyTuple::new(py, &[encoding, bytes, start, end, reason])
.unwrap()
.into_any()
.unbind()
}
}
impl PyErrArguments for alloc::string::FromUtf8Error {
fn arguments(self, py: Python<'_>) -> Py<PyAny> {
Utf8ErrorWithBytes {
err: self.utf8_error(),
bytes: self.into_bytes(),
}
.arguments(py)
}
}
impl core::convert::From<alloc::string::FromUtf8Error> for PyErr {
fn from(err: alloc::string::FromUtf8Error) -> PyErr {
exceptions::PyUnicodeDecodeError::new_err(err)
}
}
impl PyErrArguments for alloc::ffi::IntoStringError {
fn arguments(self, py: Python<'_>) -> Py<PyAny> {
Utf8ErrorWithBytes {
err: self.utf8_error(),
bytes: self.into_cstring().into_bytes(),
}
.arguments(py)
}
}
impl core::convert::From<alloc::ffi::IntoStringError> for PyErr {
fn from(err: alloc::ffi::IntoStringError) -> PyErr {
exceptions::PyUnicodeDecodeError::new_err(err)
}
}
impl_to_pyerr!(core::array::TryFromSliceError, exceptions::PyValueError);
impl_to_pyerr!(core::num::ParseIntError, exceptions::PyValueError);
impl_to_pyerr!(core::num::ParseFloatError, exceptions::PyValueError);
impl_to_pyerr!(core::num::TryFromIntError, exceptions::PyValueError);
impl_to_pyerr!(core::str::ParseBoolError, exceptions::PyValueError);
impl_to_pyerr!(alloc::ffi::NulError, exceptions::PyValueError);
impl_to_pyerr!(core::net::AddrParseError, exceptions::PyValueError);
impl_to_pyerr!(core::time::TryFromFloatSecsError, exceptions::PyValueError);
impl_to_pyerr!(std::time::SystemTimeError, exceptions::PyValueError);
impl_to_pyerr!(std::path::StripPrefixError, exceptions::PyValueError);
impl_to_pyerr!(std::env::JoinPathsError, exceptions::PyValueError);
impl_to_pyerr!(core::char::ParseCharError, exceptions::PyValueError);
impl_to_pyerr!(core::char::CharTryFromError, exceptions::PyValueError);
#[cfg(test)]
mod tests {
use super::*;
use crate::exceptions::PyUnicodeDecodeError;
use crate::types::PyAnyMethods;
use crate::{IntoPyObjectExt as _, PyErr, Python};
use std::io;
#[test]
fn io_errors() {
use crate::types::any::PyAnyMethods;
let check_err = |kind, expected_ty| {
Python::attach(|py| {
let rust_err = io::Error::new(kind, "some error msg");
let py_err: PyErr = rust_err.into();
let py_err_msg = format!("{expected_ty}: some error msg");
assert_eq!(py_err.to_string(), py_err_msg);
let py_error_clone = py_err.clone_ref(py);
let rust_err_from_py_err: io::Error = py_err.into();
assert_eq!(rust_err_from_py_err.to_string(), py_err_msg);
assert_eq!(rust_err_from_py_err.kind(), kind);
let py_err_recovered_from_rust_err: PyErr = rust_err_from_py_err.into();
assert!(py_err_recovered_from_rust_err
.value(py)
.is(py_error_clone.value(py))); // It should be the same exception
})
};
check_err(io::ErrorKind::BrokenPipe, "BrokenPipeError");
check_err(io::ErrorKind::ConnectionRefused, "ConnectionRefusedError");
check_err(io::ErrorKind::ConnectionAborted, "ConnectionAbortedError");
check_err(io::ErrorKind::ConnectionReset, "ConnectionResetError");
check_err(io::ErrorKind::Interrupted, "InterruptedError");
check_err(io::ErrorKind::NotFound, "FileNotFoundError");
check_err(io::ErrorKind::PermissionDenied, "PermissionError");
check_err(io::ErrorKind::AlreadyExists, "FileExistsError");
check_err(io::ErrorKind::WouldBlock, "BlockingIOError");
check_err(io::ErrorKind::TimedOut, "TimeoutError");
check_err(io::ErrorKind::IsADirectory, "IsADirectoryError");
check_err(io::ErrorKind::NotADirectory, "NotADirectoryError");
}
#[test]
#[allow(invalid_from_utf8)]
fn utf8_errors() {
let bytes = b"abc\xffdef".to_vec();
let check_err = |py_err: PyErr| {
Python::attach(|py| {
let py_err = py_err.into_bound_py_any(py).unwrap();
assert!(py_err.is_instance_of::<exceptions::PyUnicodeDecodeError>());
assert_eq!(
py_err
.getattr("encoding")
.unwrap()
.extract::<String>()
.unwrap(),
"utf-8"
);
assert_eq!(
py_err
.getattr("object")
.unwrap()
.extract::<Vec<u8>>()
.unwrap(),
&*bytes
);
assert_eq!(
py_err.getattr("start").unwrap().extract::<usize>().unwrap(),
3
);
assert_eq!(
py_err.getattr("end").unwrap().extract::<usize>().unwrap(),
4
);
assert_eq!(
py_err
.getattr("reason")
.unwrap()
.extract::<String>()
.unwrap(),
"invalid utf-8"
);
});
};
let utf8_err_with_bytes = PyUnicodeDecodeError::new_err(Utf8ErrorWithBytes {
err: core::str::from_utf8(&bytes).expect_err("\\xff is invalid utf-8"),
bytes: bytes.clone(),
});
check_err(utf8_err_with_bytes);
let from_utf8_err = String::from_utf8(bytes.clone())
.expect_err("\\xff is invalid utf-8")
.into();
check_err(from_utf8_err);
let from_utf8_err = alloc::ffi::CString::new(bytes.clone())
.unwrap()
.into_string()
.expect_err("\\xff is invalid utf-8")
.into();
check_err(from_utf8_err);
}
#[test]
fn std_error_conversions() {
Python::attach(|py| {
let check_err = |err: PyErr, expected_msg: &str| {
let py_err = err.into_bound_py_any(py).unwrap();
assert!(py_err.is_instance_of::<exceptions::PyValueError>());
let msg = py_err.str().unwrap().to_string();
assert_eq!(msg, expected_msg);
};
// TryFromFloatSecsError
let float_secs_err = core::time::Duration::try_from_secs_f32(-1.0).unwrap_err();
let expected = float_secs_err.to_string();
check_err(float_secs_err.into(), &expected);
// SystemTimeError
let sys_time_err = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::now() + core::time::Duration::from_secs(1))
.unwrap_err();
let expected = sys_time_err.to_string();
check_err(sys_time_err.into(), &expected);
// StripPrefixError
let strip_prefix_err = std::path::Path::new("/a/b/c")
.strip_prefix("/x/y/z")
.unwrap_err();
let expected = strip_prefix_err.to_string();
check_err(strip_prefix_err.into(), &expected);
// JoinPathsError
let join_paths_err = std::env::join_paths(["a:b", "a;b", "a\"b"].iter()).unwrap_err();
let expected = join_paths_err.to_string();
check_err(join_paths_err.into(), &expected);
// ParseCharError
let parse_char_err = "abc".parse::<char>().unwrap_err();
let expected = parse_char_err.to_string();
check_err(parse_char_err.into(), &expected);
// CharTryFromError
let char_try_from_err = char::try_from(0xD800_u32).unwrap_err();
let expected = char_try_from_err.to_string();
check_err(char_try_from_err.into(), &expected);
});
}
}
+1081
View File
File diff suppressed because it is too large. Load diff
+1304
View File
File diff suppressed because it is too large. Load diff
+27
View File
@@ -0,0 +1,27 @@
//! Raw FFI declarations for Python's C API.
//!
//! This module provides low level bindings to the Python interpreter.
//! It is meant for advanced users only - regular PyO3 users shouldn't
//! need to interact with this module at all.
//!
//! The contents of this module are not documented here, as it would entail
//! basically copying the documentation from CPython. Consult the [Python/C API Reference
//! Manual][capi] for up-to-date documentation.
//!
//! # Safety
//!
//! The functions in this module lack individual safety documentation, but
//! generally the following apply:
//! - Pointer arguments have to point to a valid Python object of the correct type,
//! although null pointers are sometimes valid input.
//! - The vast majority can only be used safely while the GIL is held.
//! - Some functions have additional safety requirements, consult the
//! [Python/C API Reference Manual][capi] for more information.
//!
//! [capi]: https://docs.python.org/3/c-api/index.html
#[cfg(test)]
mod tests;
// reexport raw bindings exposed in pyo3_ffi
pub use pyo3_ffi::*;
+340
View File
@@ -0,0 +1,340 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
use crate::ffi::*;
use crate::Python;
#[cfg(not(Py_LIMITED_API))]
use crate::types::any::PyAnyMethods;
#[cfg(all(not(Py_LIMITED_API), any(not(any(PyPy, GraalPy)), feature = "macros")))]
use crate::types::PyString;
#[cfg(not(Py_LIMITED_API))]
use crate::{types::PyDict, Bound, PyAny};
#[cfg(not(any(Py_3_12, Py_LIMITED_API, GraalPy)))]
use libc::wchar_t;
#[cfg(not(Py_LIMITED_API))]
#[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
#[test]
fn test_datetime_fromtimestamp() {
use crate::IntoPyObject;
Python::attach(|py| {
let args = (100,).into_pyobject(py).unwrap();
let dt = unsafe {
PyDateTime_IMPORT();
Bound::from_owned_ptr(py, PyDateTime_FromTimestamp(args.as_ptr()))
};
let locals = PyDict::new(py);
locals.set_item("dt", dt).unwrap();
py.run(
c"import datetime; assert dt == datetime.datetime.fromtimestamp(100)",
None,
Some(&locals),
)
.unwrap();
})
}
#[cfg(not(Py_LIMITED_API))]
#[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
#[test]
fn test_date_fromtimestamp() {
use crate::IntoPyObject;
Python::attach(|py| {
let args = (100,).into_pyobject(py).unwrap();
let dt = unsafe {
PyDateTime_IMPORT();
Bound::from_owned_ptr(py, PyDate_FromTimestamp(args.as_ptr()))
};
let locals = PyDict::new(py);
locals.set_item("dt", dt).unwrap();
py.run(
c"import datetime; assert dt == datetime.date.fromtimestamp(100)",
None,
Some(&locals),
)
.unwrap();
})
}
#[cfg(not(Py_LIMITED_API))]
#[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
#[test]
fn test_utc_timezone() {
Python::attach(|py| {
let utc_timezone: Bound<'_, PyAny> = unsafe {
PyDateTime_IMPORT();
Bound::from_borrowed_ptr(py, PyDateTime_TimeZone_UTC())
};
let locals = PyDict::new(py);
locals.set_item("utc_timezone", utc_timezone).unwrap();
py.run(
c"import datetime; assert utc_timezone is datetime.timezone.utc",
None,
Some(&locals),
)
.unwrap();
})
}
#[test]
#[cfg(not(Py_LIMITED_API))]
#[cfg(feature = "macros")]
#[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
fn test_timezone_from_offset() {
use crate::{ffi_ptr_ext::FfiPtrExt, types::PyDelta};
Python::attach(|py| {
let delta = PyDelta::new(py, 0, 100, 0, false).unwrap();
let tz = unsafe { PyTimeZone_FromOffset(delta.as_ptr()).assume_owned(py) };
crate::py_run!(
py,
tz,
"import datetime; assert tz == datetime.timezone(datetime.timedelta(seconds=100))"
);
})
}
#[test]
#[cfg(not(Py_LIMITED_API))]
#[cfg(feature = "macros")]
#[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
fn test_timezone_from_offset_and_name() {
use crate::{ffi_ptr_ext::FfiPtrExt, types::PyDelta};
Python::attach(|py| {
let delta = PyDelta::new(py, 0, 100, 0, false).unwrap();
let tzname = PyString::new(py, "testtz");
let tz = unsafe {
PyTimeZone_FromOffsetAndName(delta.as_ptr(), tzname.as_ptr()).assume_owned(py)
};
crate::py_run!(
py,
tz,
"import datetime; assert tz == datetime.timezone(datetime.timedelta(seconds=100), 'testtz')"
);
})
}
#[test]
#[cfg(not(any(Py_LIMITED_API, GraalPy)))]
fn ascii_object_bitfield() {
let ob_base: PyObject = unsafe { core::mem::zeroed() };
#[cfg_attr(Py_3_14, allow(unused_mut, unused_variables))]
let mut o = PyASCIIObject {
ob_base,
length: 0,
#[cfg(any(Py_3_11, not(PyPy)))]
hash: 0,
state: 0u32,
#[cfg(not(Py_3_12))]
wstr: core::ptr::null_mut() as *mut wchar_t,
};
#[cfg(not(Py_3_14))]
unsafe {
assert_eq!(o.interned(), 0);
assert_eq!(o.kind(), 0);
assert_eq!(o.compact(), 0);
assert_eq!(o.ascii(), 0);
#[cfg(not(Py_3_12))]
assert_eq!(o.ready(), 0);
let interned_count = if cfg!(Py_3_12) { 2 } else { 4 };
for i in 0..interned_count {
o.set_interned(i);
assert_eq!(o.interned(), i);
}
for i in 0..8 {
o.set_kind(i);
assert_eq!(o.kind(), i);
}
o.set_compact(1);
assert_eq!(o.compact(), 1);
o.set_ascii(1);
assert_eq!(o.ascii(), 1);
#[cfg(not(Py_3_12))]
o.set_ready(1);
#[cfg(not(Py_3_12))]
assert_eq!(o.ready(), 1);
#[cfg(Py_3_12)]
o.set_statically_allocated(1);
#[cfg(Py_3_12)]
assert_eq!(o.statically_allocated(), 1);
}
}
#[test]
#[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
fn ascii() {
Python::attach(|py| {
// This test relies on implementation details of PyString.
let s = PyString::new(py, "hello, world");
let ptr = s.as_ptr();
unsafe {
#[cfg(not(Py_3_14))]
{
let ascii_ptr = ptr as *mut PyASCIIObject;
let ascii = ascii_ptr.as_ref().unwrap();
assert_eq!(ascii.interned(), 0);
assert_eq!(ascii.kind(), PyUnicode_1BYTE_KIND);
assert_eq!(ascii.compact(), 1);
assert_eq!(ascii.ascii(), 1);
#[cfg(not(Py_3_12))]
assert_eq!(ascii.ready(), 1);
assert_eq!(PyUnicode_IS_ASCII(ptr), 1);
assert_eq!(PyUnicode_IS_COMPACT(ptr), 1);
assert_eq!(PyUnicode_IS_COMPACT_ASCII(ptr), 1);
}
assert!(!PyUnicode_1BYTE_DATA(ptr).is_null());
// 2 and 4 byte macros return nonsense for this string instance.
assert_eq!(PyUnicode_KIND(ptr), PyUnicode_1BYTE_KIND);
assert!(!PyUnicode_DATA(ptr).is_null());
assert_eq!(PyUnicode_GET_LENGTH(ptr), s.len().unwrap() as Py_ssize_t);
#[cfg(not(Py_3_12))]
assert_eq!(PyUnicode_IS_READY(ptr), 1);
// This has potential to mutate object. But it should be a no-op since
// we're already ready.
#[cfg(not(Py_3_12))]
assert_eq!(PyUnicode_READY(ptr), 0);
}
})
}
#[test]
#[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
fn ucs4() {
Python::attach(|py| {
let s = "哈哈🐈";
let py_string = PyString::new(py, s);
let ptr = py_string.as_ptr();
unsafe {
#[cfg(not(Py_3_14))]
{
let ascii_ptr = ptr as *mut PyASCIIObject;
let ascii = ascii_ptr.as_ref().unwrap();
assert_eq!(ascii.interned(), 0);
assert_eq!(ascii.kind(), PyUnicode_4BYTE_KIND);
assert_eq!(ascii.compact(), 1);
assert_eq!(ascii.ascii(), 0);
#[cfg(not(Py_3_12))]
assert_eq!(ascii.ready(), 1);
assert_eq!(PyUnicode_IS_ASCII(ptr), 0);
assert_eq!(PyUnicode_IS_COMPACT(ptr), 1);
assert_eq!(PyUnicode_IS_COMPACT_ASCII(ptr), 0);
}
assert!(!PyUnicode_4BYTE_DATA(ptr).is_null());
assert_eq!(PyUnicode_KIND(ptr), PyUnicode_4BYTE_KIND);
assert!(!PyUnicode_DATA(ptr).is_null());
assert_eq!(
PyUnicode_GET_LENGTH(ptr),
py_string.len().unwrap() as Py_ssize_t
);
#[cfg(not(Py_3_12))]
assert_eq!(PyUnicode_IS_READY(ptr), 1);
// This has potential to mutate object. But it should be a no-op since
// we're already ready.
#[cfg(not(Py_3_12))]
assert_eq!(PyUnicode_READY(ptr), 0);
}
})
}
#[test]
#[cfg(not(Py_LIMITED_API))]
#[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
fn test_get_tzinfo() {
use crate::types::PyTzInfo;
crate::Python::attach(|py| {
use crate::types::{PyDateTime, PyTime};
let utc: &Bound<'_, _> = &PyTzInfo::utc(py).unwrap();
let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, Some(utc)).unwrap();
assert!(
unsafe { Bound::from_borrowed_ptr(py, PyDateTime_DATE_GET_TZINFO(dt.as_ptr())) }
.is(utc)
);
let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, None).unwrap();
assert!(
unsafe { Bound::from_borrowed_ptr(py, PyDateTime_DATE_GET_TZINFO(dt.as_ptr())) }
.is_none()
);
let t = PyTime::new(py, 0, 0, 0, 0, Some(utc)).unwrap();
assert!(
unsafe { Bound::from_borrowed_ptr(py, PyDateTime_TIME_GET_TZINFO(t.as_ptr())) }.is(utc)
);
let t = PyTime::new(py, 0, 0, 0, 0, None).unwrap();
assert!(
unsafe { Bound::from_borrowed_ptr(py, PyDateTime_TIME_GET_TZINFO(t.as_ptr())) }
.is_none()
);
})
}
#[test]
fn test_inc_dec_ref() {
Python::attach(|py| {
let obj = py.eval(c"object()", None, None).unwrap();
let ref_count = obj._get_refcnt();
let ptr = obj.as_ptr();
unsafe { Py_INCREF(ptr) };
assert_eq!(obj._get_refcnt(), ref_count + 1);
unsafe { Py_DECREF(ptr) };
assert_eq!(obj._get_refcnt(), ref_count);
})
}
#[test]
#[cfg(Py_3_12)]
fn test_inc_dec_ref_immortal() {
Python::attach(|py| {
let obj = py.None();
let ref_count = obj._get_refcnt(py);
let ptr = obj.as_ptr();
unsafe { Py_INCREF(ptr) };
assert_eq!(obj._get_refcnt(py), ref_count);
unsafe { Py_DECREF(ptr) };
assert_eq!(obj._get_refcnt(py), ref_count);
})
}
+117
View File
@@ -0,0 +1,117 @@
use crate::sealed::Sealed;
use crate::{
ffi,
instance::{Borrowed, Bound},
PyAny, PyResult, Python,
};
pub(crate) trait FfiPtrExt: Sealed {
/// Assumes this pointer carries a Python reference which needs to be decref'd.
///
/// If the pointer is NULL, this function will fetch an error.
unsafe fn assume_owned_or_err(self, py: Python<'_>) -> PyResult<Bound<'_, PyAny>>;
/// Same as `assume_owned_or_err`, but doesn't fetch an error on NULL.
unsafe fn assume_owned_or_opt(self, py: Python<'_>) -> Option<Bound<'_, PyAny>>;
/// Same as `assume_owned_or_err`, but panics on NULL.
unsafe fn assume_owned(self, py: Python<'_>) -> Bound<'_, PyAny>;
/// Same as `assume_owned_or_err`, but does not check for NULL.
unsafe fn assume_owned_unchecked(self, py: Python<'_>) -> Bound<'_, PyAny>;
/// Assumes this pointer is borrowed from a parent object.
///
/// Warning: the lifetime `'a` is not bounded by the function arguments; the caller is
/// responsible to ensure this is tied to some appropriate lifetime.
unsafe fn assume_borrowed_or_err<'a>(self, py: Python<'_>)
-> PyResult<Borrowed<'a, '_, PyAny>>;
/// Same as `assume_borrowed_or_err`, but doesn't fetch an error on NULL.
unsafe fn assume_borrowed_or_opt<'a>(self, py: Python<'_>) -> Option<Borrowed<'a, '_, PyAny>>;
/// Same as `assume_borrowed_or_err`, but panics on NULL.
unsafe fn assume_borrowed<'a>(self, py: Python<'_>) -> Borrowed<'a, '_, PyAny>;
/// Same as `assume_borrowed_or_err`, but does not check for NULL.
unsafe fn assume_borrowed_unchecked<'a>(self, py: Python<'_>) -> Borrowed<'a, '_, PyAny>;
}
impl FfiPtrExt for *mut ffi::PyObject {
/// # Safety
///
/// see requirements for [`Bound::from_owned_ptr_or_err`]
#[inline]
unsafe fn assume_owned_or_err(self, py: Python<'_>) -> PyResult<Bound<'_, PyAny>> {
// SAFETY: caller upholds requirements
unsafe { Bound::from_owned_ptr_or_err(py, self) }
}
/// # Safety
///
/// see requirements for [`Bound::from_owned_ptr_or_opt`]
#[inline]
unsafe fn assume_owned_or_opt(self, py: Python<'_>) -> Option<Bound<'_, PyAny>> {
// SAFETY: caller upholds requirements
unsafe { Bound::from_owned_ptr_or_opt(py, self) }
}
/// # Safety
///
/// see requirements for [`Bound::from_owned_ptr`]
#[inline]
#[track_caller]
unsafe fn assume_owned(self, py: Python<'_>) -> Bound<'_, PyAny> {
// SAFETY: caller upholds requirements
unsafe { Bound::from_owned_ptr(py, self) }
}
/// # Safety
///
/// see requirements for [`Bound::from_owned_ptr_unchecked`]
#[inline]
unsafe fn assume_owned_unchecked(self, py: Python<'_>) -> Bound<'_, PyAny> {
// SAFETY: caller upholds requirements
unsafe { Bound::from_owned_ptr_unchecked(py, self) }
}
/// # Safety
///
/// see requirements for [`Borrowed::from_ptr_or_err`]
#[inline]
unsafe fn assume_borrowed_or_err<'a>(
self,
py: Python<'_>,
) -> PyResult<Borrowed<'a, '_, PyAny>> {
// SAFETY: caller upholds requirements
unsafe { Borrowed::from_ptr_or_err(py, self) }
}
/// # Safety
///
/// see requirements for [`Borrowed::from_ptr_or_opt`]
#[inline]
unsafe fn assume_borrowed_or_opt<'a>(self, py: Python<'_>) -> Option<Borrowed<'a, '_, PyAny>> {
// SAFETY: caller upholds requirements
unsafe { Borrowed::from_ptr_or_opt(py, self) }
}
/// # Safety
///
/// see requirements for [`Borrowed::from_ptr`]
#[inline]
#[track_caller]
unsafe fn assume_borrowed<'a>(self, py: Python<'_>) -> Borrowed<'a, '_, PyAny> {
// SAFETY: caller upholds requirements
unsafe { Borrowed::from_ptr(py, self) }
}
/// # Safety
///
/// see requirements for [`Borrowed::from_ptr_unchecked`]
#[inline]
unsafe fn assume_borrowed_unchecked<'a>(self, py: Python<'_>) -> Borrowed<'a, '_, PyAny> {
// SAFETY: caller upholds requirements
unsafe { Borrowed::from_ptr_unchecked(py, self) }
}
}
+228
View File
@@ -0,0 +1,228 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
#[cfg(any(doc, all(Py_3_14, not(Py_LIMITED_API))))]
use crate::{types::PyString, Python};
#[cfg(all(Py_3_14, not(Py_LIMITED_API)))]
use {
crate::ffi::{
PyUnicodeWriter_Create, PyUnicodeWriter_Discard, PyUnicodeWriter_Finish,
PyUnicodeWriter_WriteChar, PyUnicodeWriter_WriteUTF8,
},
crate::ffi_ptr_ext::FfiPtrExt,
crate::py_result_ext::PyResultExt,
crate::IntoPyObject,
crate::{ffi, Bound, PyErr, PyResult},
core::fmt,
core::mem::ManuallyDrop,
core::ptr::NonNull,
};
/// This macro is analogous to Rust's [`format!`] macro, but returns a [`PyString`] instead of a [`String`].
///
/// # Arguments
///
/// The arguments are exactly like [`format!`], but with `py` (a [`Python`] token) as the first argument:
///
/// # Interning Advantage
///
/// If the format string is a static string and all arguments are constant at compile time,
/// this macro will intern the string in Python, offering better performance and memory usage
/// compared to [`PyString::from_fmt`].
///
/// ```rust
/// # use pyo3::{py_format, Python, types::PyString, Bound};
/// Python::attach(|py| {
/// let py_string: Bound<'_, PyString> = py_format!(py, "{} {}", "hello", "world").unwrap();
/// assert_eq!(py_string.to_string(), "hello world");
/// });
/// ```
#[macro_export]
macro_rules! py_format {
($py: expr, $($arg:tt)*) => {{
if let Some(static_string) = format_args!($($arg)*).as_str() {
static INTERNED: $crate::sync::PyOnceLock<$crate::Py<$crate::types::PyString>> = $crate::sync::PyOnceLock::new();
Ok(
INTERNED
.get_or_init($py, || $crate::types::PyString::intern($py, static_string).unbind())
.bind($py)
.to_owned()
)
} else {
$crate::types::PyString::from_fmt($py, format_args!($($arg)*))
}
}}
}
#[cfg(all(Py_3_14, not(Py_LIMITED_API)))]
/// The `PyUnicodeWriter` is a utility for efficiently constructing Python strings
pub(crate) struct PyUnicodeWriter<'py> {
python: Python<'py>,
writer: NonNull<ffi::PyUnicodeWriter>,
last_error: Option<PyErr>,
}
#[cfg(all(Py_3_14, not(Py_LIMITED_API)))]
impl<'py> PyUnicodeWriter<'py> {
/// Creates a new `PyUnicodeWriter`.
pub fn new(py: Python<'py>) -> PyResult<Self> {
Self::with_capacity(py, 0)
}
/// Creates a new `PyUnicodeWriter` with the specified initial capacity.
#[inline]
pub fn with_capacity(py: Python<'py>, capacity: usize) -> PyResult<Self> {
match NonNull::new(unsafe { PyUnicodeWriter_Create(capacity.try_into()?) }) {
Some(ptr) => Ok(PyUnicodeWriter {
python: py,
writer: ptr,
last_error: None,
}),
None => Err(PyErr::fetch(py)),
}
}
/// Consumes the `PyUnicodeWriter` and returns a `Bound<PyString>` containing the constructed string.
#[inline]
pub fn into_py_string(mut self) -> PyResult<Bound<'py, PyString>> {
let py = self.python;
if let Some(error) = self.take_error() {
Err(error)
} else {
unsafe {
PyUnicodeWriter_Finish(ManuallyDrop::new(self).as_ptr())
.assume_owned_or_err(py)
.cast_into_unchecked()
}
}
}
/// When fmt::Write returned an error, this function can be used to retrieve the last error that occurred.
#[inline]
pub fn take_error(&mut self) -> Option<PyErr> {
self.last_error.take()
}
#[inline]
fn as_ptr(&self) -> *mut ffi::PyUnicodeWriter {
self.writer.as_ptr()
}
#[inline]
fn set_error(&mut self) {
self.last_error = Some(PyErr::fetch(self.python));
}
}
#[cfg(all(Py_3_14, not(Py_LIMITED_API)))]
impl fmt::Write for PyUnicodeWriter<'_> {
#[inline]
fn write_str(&mut self, s: &str) -> fmt::Result {
let result = unsafe {
PyUnicodeWriter_WriteUTF8(self.as_ptr(), s.as_ptr().cast(), s.len() as isize)
};
if result < 0 {
self.set_error();
Err(fmt::Error)
} else {
Ok(())
}
}
#[inline]
fn write_char(&mut self, c: char) -> fmt::Result {
let result = unsafe { PyUnicodeWriter_WriteChar(self.as_ptr(), c.into()) };
if result < 0 {
self.set_error();
Err(fmt::Error)
} else {
Ok(())
}
}
}
#[cfg(all(Py_3_14, not(Py_LIMITED_API)))]
impl Drop for PyUnicodeWriter<'_> {
#[inline]
fn drop(&mut self) {
unsafe {
PyUnicodeWriter_Discard(self.as_ptr());
}
}
}
#[cfg(all(Py_3_14, not(Py_LIMITED_API)))]
impl<'py> IntoPyObject<'py> for PyUnicodeWriter<'py> {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
#[inline]
fn into_pyobject(self, _py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
self.into_py_string()
}
}
#[cfg(all(Py_3_14, not(Py_LIMITED_API)))]
impl<'py> TryInto<Bound<'py, PyString>> for PyUnicodeWriter<'py> {
type Error = PyErr;
#[inline]
fn try_into(self) -> PyResult<Bound<'py, PyString>> {
self.into_py_string()
}
}
#[cfg(test)]
mod tests {
#[cfg(all(Py_3_14, not(Py_LIMITED_API)))]
use super::*;
use crate::types::PyStringMethods;
use crate::{IntoPyObject, Python};
#[test]
#[allow(clippy::write_literal)]
#[cfg(all(Py_3_14, not(Py_LIMITED_API)))]
fn unicode_writer_test() {
use core::fmt::Write;
Python::attach(|py| {
let mut writer = PyUnicodeWriter::new(py).unwrap();
write!(writer, "Hello {}!", "world").unwrap();
writer.write_char('😎').unwrap();
let result = writer.into_py_string().unwrap();
assert_eq!(result.to_string(), "Hello world!😎");
});
}
#[test]
#[allow(clippy::write_literal)]
#[cfg(all(Py_3_14, not(Py_LIMITED_API)))]
fn unicode_writer_with_capacity() {
use core::fmt::Write;
Python::attach(|py| {
let mut writer = PyUnicodeWriter::with_capacity(py, 10).unwrap();
write!(writer, "Hello {}!", "world").unwrap();
writer.write_char('😎').unwrap();
let result = writer.into_py_string().unwrap();
assert_eq!(result.to_string(), "Hello world!😎");
});
}
#[test]
fn test_pystring_from_fmt() {
Python::attach(|py| {
py_format!(py, "Hello {}!", "world").unwrap();
});
}
#[test]
fn test_complex_format() {
Python::attach(|py| {
let complex_value = (42, "foo", [0; 0]).into_pyobject(py).unwrap();
let py_string = py_format!(py, "This is some complex value: {complex_value}").unwrap();
let actual = py_string.to_cow().unwrap();
let expected = "This is some complex value: (42, 'foo', [])";
assert_eq!(actual, expected);
});
}
}
+30
View File
@@ -0,0 +1,30 @@
#![allow(missing_docs)]
//! Internals of PyO3 which are accessed by code expanded from PyO3's procedural macros.
//!
//! Usage of any of these APIs in downstream code is implicitly acknowledging that these
//! APIs may may change at any time without documentation in the CHANGELOG and without
//! breaking semver guarantees.
pub mod callback;
pub mod concat;
#[cfg(feature = "experimental-async")]
pub mod coroutine;
pub mod deprecated;
pub mod exceptions;
pub mod extract_argument;
pub mod freelist;
pub mod frompyobject;
#[cfg(feature = "experimental-inspect")]
pub mod introspection;
pub mod panic;
pub mod pycell;
pub mod pyclass;
pub mod pyclass_init;
pub mod pyfunction;
pub mod pymethods;
pub mod pymodule;
#[doc(hidden)]
pub mod trampoline;
pub mod unindent;
pub mod wrap;
+225
View File
@@ -0,0 +1,225 @@
//! Utilities for a Python callable object that invokes a Rust function.
use crate::err::{PyErr, PyResult};
use crate::exceptions::PyOverflowError;
use crate::ffi::{self, Py_hash_t};
use crate::{BoundObject, IntoPyObject, Py, PyAny, Python};
use core::ffi::c_int;
/// A type which can be the return type of a python C-API callback
pub trait PyCallbackOutput: Copy + py_callback_output::Sealed {
/// The error value to return to python if the callback raised an exception
const ERR_VALUE: Self;
}
/// Seals `PyCallbackOutput` so that types outside PyO3 cannot implement it.
mod py_callback_output {
use core::ffi::c_int;
use pyo3_ffi::Py_ssize_t;
use crate::ffi::PyObject;
pub trait Sealed {}
impl Sealed for *mut PyObject {}
impl Sealed for c_int {}
impl Sealed for Py_ssize_t {}
}
impl PyCallbackOutput for *mut ffi::PyObject {
const ERR_VALUE: Self = core::ptr::null_mut();
}
impl PyCallbackOutput for core::ffi::c_int {
const ERR_VALUE: Self = -1;
}
impl PyCallbackOutput for ffi::Py_ssize_t {
const ERR_VALUE: Self = -1;
}
/// Convert the result of callback function into the appropriate return value.
pub trait IntoPyCallbackOutput<'py, Target>: into_py_callback_output::Sealed<'py, Target> {
fn convert(self, py: Python<'py>) -> PyResult<Target>;
}
/// Seals `IntoPyCallbackOutput` so that types outside PyO3 cannot implement it.
mod into_py_callback_output {
use pyo3_ffi::Py_hash_t;
use crate::{
ffi,
impl_::callback::{HashCallbackOutput, IntoPyCallbackOutput, WrappingCastTo},
IntoPyObject, Py, PyAny, PyErr,
};
pub trait Sealed<'py, Target> {}
impl<'py, T: IntoPyObject<'py>> Sealed<'py, *mut ffi::PyObject> for T {}
impl<'py, T: IntoPyCallbackOutput<'py, U>, E: Into<PyErr>, U> Sealed<'py, U> for Result<T, E> {}
impl Sealed<'_, Self> for *mut ffi::PyObject {}
impl Sealed<'_, core::ffi::c_int> for () {}
impl Sealed<'_, core::ffi::c_int> for bool {}
impl Sealed<'_, ()> for () {}
impl Sealed<'_, ffi::Py_ssize_t> for usize {}
impl Sealed<'_, bool> for bool {}
impl Sealed<'_, usize> for usize {}
impl<'py, T: IntoPyObject<'py>> Sealed<'py, Py<PyAny>> for T {}
impl Sealed<'_, Py_hash_t> for HashCallbackOutput {}
impl<T: WrappingCastTo<Py_hash_t>> Sealed<'_, HashCallbackOutput> for T {}
}
impl<'py, T, E, U> IntoPyCallbackOutput<'py, U> for Result<T, E>
where
T: IntoPyCallbackOutput<'py, U>,
E: Into<PyErr>,
{
#[inline]
fn convert(self, py: Python<'py>) -> PyResult<U> {
match self {
Ok(v) => v.convert(py),
Err(e) => Err(e.into()),
}
}
}
impl<'py, T> IntoPyCallbackOutput<'py, *mut ffi::PyObject> for T
where
T: IntoPyObject<'py>,
{
#[inline]
fn convert(self, py: Python<'py>) -> PyResult<*mut ffi::PyObject> {
self.into_pyobject(py)
.map(BoundObject::into_ptr)
.map_err(Into::into)
}
}
impl IntoPyCallbackOutput<'_, Self> for *mut ffi::PyObject {
#[inline]
fn convert(self, _: Python<'_>) -> PyResult<Self> {
Ok(self)
}
}
impl IntoPyCallbackOutput<'_, core::ffi::c_int> for () {
#[inline]
fn convert(self, _: Python<'_>) -> PyResult<core::ffi::c_int> {
Ok(0)
}
}
impl IntoPyCallbackOutput<'_, core::ffi::c_int> for bool {
#[inline]
fn convert(self, _: Python<'_>) -> PyResult<core::ffi::c_int> {
Ok(self as c_int)
}
}
impl IntoPyCallbackOutput<'_, ()> for () {
#[inline]
fn convert(self, _: Python<'_>) -> PyResult<()> {
Ok(())
}
}
impl IntoPyCallbackOutput<'_, ffi::Py_ssize_t> for usize {
#[inline]
fn convert(self, _py: Python<'_>) -> PyResult<ffi::Py_ssize_t> {
self.try_into().map_err(|_err| PyOverflowError::new_err(()))
}
}
// Conversion traits needed by pyo3's macros
impl IntoPyCallbackOutput<'_, bool> for bool {
#[inline]
fn convert(self, _: Python<'_>) -> PyResult<bool> {
Ok(self)
}
}
impl IntoPyCallbackOutput<'_, usize> for usize {
#[inline]
fn convert(self, _: Python<'_>) -> PyResult<usize> {
Ok(self)
}
}
impl<'py, T> IntoPyCallbackOutput<'py, Py<PyAny>> for T
where
T: IntoPyObject<'py>,
{
#[inline]
fn convert(self, py: Python<'py>) -> PyResult<Py<PyAny>> {
self.into_pyobject(py)
.map(BoundObject::into_any)
.map(BoundObject::unbind)
.map_err(Into::into)
}
}
pub trait WrappingCastTo<T>: wrapping_cast_to::Sealed<T> {
fn wrapping_cast(self) -> T;
}
/// Seals `WrappingCastTo` so that types outside PyO3 cannot implement it.
mod wrapping_cast_to {
pub trait Sealed<T> {}
}
macro_rules! wrapping_cast {
($from:ty, $to:ty) => {
impl WrappingCastTo<$to> for $from {
#[inline]
fn wrapping_cast(self) -> $to {
self as $to
}
}
impl wrapping_cast_to::Sealed<$to> for $from {}
};
}
wrapping_cast!(u8, Py_hash_t);
wrapping_cast!(u16, Py_hash_t);
wrapping_cast!(u32, Py_hash_t);
wrapping_cast!(usize, Py_hash_t);
wrapping_cast!(u64, Py_hash_t);
wrapping_cast!(i8, Py_hash_t);
wrapping_cast!(i16, Py_hash_t);
wrapping_cast!(i32, Py_hash_t);
wrapping_cast!(isize, Py_hash_t);
wrapping_cast!(i64, Py_hash_t);
pub struct HashCallbackOutput(Py_hash_t);
impl IntoPyCallbackOutput<'_, Py_hash_t> for HashCallbackOutput {
#[inline]
fn convert(self, _py: Python<'_>) -> PyResult<Py_hash_t> {
let hash = self.0;
if hash == -1 {
Ok(-2)
} else {
Ok(hash)
}
}
}
impl<T> IntoPyCallbackOutput<'_, HashCallbackOutput> for T
where
T: WrappingCastTo<Py_hash_t>,
{
#[inline]
fn convert(self, _py: Python<'_>) -> PyResult<HashCallbackOutput> {
Ok(HashCallbackOutput(self.wrapping_cast()))
}
}
#[doc(hidden)]
#[inline]
pub fn convert<'py, T, U>(py: Python<'py>, value: T) -> PyResult<U>
where
T: IntoPyCallbackOutput<'py, U>,
{
value.convert(py)
}
+82
View File
@@ -0,0 +1,82 @@
/// Calculates the total byte length of all byte pieces in the array.
///
/// This is a useful utility in order to determine the size needed for the constant
/// `combine` function.
pub const fn combined_len(pieces: &[&[u8]]) -> usize {
let mut len = 0;
let mut pieces_idx = 0;
while pieces_idx < pieces.len() {
len += pieces[pieces_idx].len();
pieces_idx += 1;
}
len
}
/// Combines all bytes pieces into a single byte array.
///
/// `out` should be a buffer at the correct size of `combined_len(pieces)`, else this will panic.
const fn combine(pieces: &[&[u8]], mut out: &mut [u8]) {
let mut pieces_idx = 0;
while pieces_idx < pieces.len() {
let piece = pieces[pieces_idx];
slice_copy_from_slice(out, piece);
// using split_at_mut because range indexing not yet supported in const fn
out = out.split_at_mut(piece.len()).1;
pieces_idx += 1;
}
// should be no trailing buffer
assert!(out.is_empty(), "output buffer too large");
}
/// Wrapper around `combine` which has a const generic parameter, this is going to be more codegen
/// at compile time (?)
pub const fn combine_to_array<const LEN: usize>(pieces: &[&[u8]]) -> [u8; LEN] {
let mut out: [u8; LEN] = [0u8; LEN];
combine(pieces, &mut out);
out
}
/// Replacement for `slice::copy_from_slice`, which is const from 1.87
pub(crate) const fn slice_copy_from_slice(out: &mut [u8], src: &[u8]) {
let mut i = 0;
while i < src.len() {
out[i] = src[i];
i += 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_combined_len() {
let pieces: [&[u8]; 3] = [b"foo", b"bar", b"baz"];
assert_eq!(combined_len(&pieces), 9);
let empty: [&[u8]; 0] = [];
assert_eq!(combined_len(&empty), 0);
}
#[test]
fn test_combine_to_array() {
let pieces: [&[u8]; 2] = [b"foo", b"bar"];
let combined = combine_to_array::<6>(&pieces);
assert_eq!(&combined, b"foobar");
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn test_combine_to_array_buffer_too_small() {
let pieces: [&[u8]; 2] = [b"foo", b"bar"];
// Intentionally wrong length
let _ = combine_to_array::<5>(&pieces);
}
#[test]
#[should_panic(expected = "output buffer too large")]
fn test_combine_to_array_buffer_too_big() {
let pieces: [&[u8]; 2] = [b"foo", b"bar"];
// Intentionally wrong length
let _ = combine_to_array::<10>(&pieces);
}
}
+36
View File
@@ -0,0 +1,36 @@
use core::future::Future;
use crate::{
coroutine::{cancel::ThrowCallback, Coroutine},
instance::Bound,
types::PyString,
Py, PyAny, PyResult, Python,
};
pub fn new_coroutine<'py, F>(
name: &Bound<'py, PyString>,
qualname_prefix: Option<&'static str>,
throw_callback: Option<ThrowCallback>,
future: F,
) -> Coroutine
where
F: Future<Output = PyResult<Py<PyAny>>> + Send + 'static,
{
Coroutine::new(Some(name.clone()), qualname_prefix, throw_callback, future)
}
/// Handle which assumes that the coroutine is attached to the thread. Unlike `Python<'_>`, this is `Send`.
pub struct AssumeAttachedInCoroutine(());
impl AssumeAttachedInCoroutine {
/// Safety: this should only be used inside a future passed to `new_coroutine`, where the coroutine is
/// guaranteed to be attached to the thread when polled.
pub unsafe fn new() -> Self {
Self(())
}
pub fn py(&self) -> Python<'_> {
// Safety: this type holds the invariant that the thread is attached
unsafe { Python::assume_attached() }
}
}
+13
View File
@@ -0,0 +1,13 @@
pub struct HasAutomaticFromPyObject<const IS_CLONE: bool> {}
impl HasAutomaticFromPyObject<true> {
#[deprecated(
since = "0.28.0",
note = "The `FromPyObject` implementation for `#[pyclass]` types which implement `Clone` is changing to an opt-in option. Use `#[pyclass(from_py_object)]` to opt-in to the `FromPyObject` derive now, or `#[pyclass(skip_from_py_object)]` to skip the `FromPyObject` implementation."
)]
pub const MSG: () = ();
}
impl HasAutomaticFromPyObject<false> {
pub const MSG: () = ();
}
+28
View File
@@ -0,0 +1,28 @@
use crate::{sync::PyOnceLock, types::PyType, Bound, Py, Python};
pub struct ImportedExceptionTypeObject {
imported_value: PyOnceLock<Py<PyType>>,
module: &'static str,
name: &'static str,
}
impl ImportedExceptionTypeObject {
pub const fn new(module: &'static str, name: &'static str) -> Self {
Self {
imported_value: PyOnceLock::new(),
module,
name,
}
}
pub fn get<'py>(&self, py: Python<'py>) -> &Bound<'py, PyType> {
self.imported_value
.import(py, self.module, self.name)
.unwrap_or_else(|e| {
panic!(
"failed to import exception {}.{}: {}",
self.module, self.name, e
)
})
}
}
File diff suppressed because it is too large. Load diff
+63
View File
@@ -0,0 +1,63 @@
//! Support for [free allocation lists][1].
//!
//! This can improve performance for types that are often created and deleted in quick succession.
//!
//! Rather than implementing this manually,
//! implement it by annotating a struct with `#[pyclass(freelist = N)]`,
//! where `N` is the size of the freelist.
//!
//! [1]: https://en.wikipedia.org/wiki/Free_list
use crate::ffi;
use core::ptr::NonNull;
/// A free allocation list for PyObject ffi pointers.
///
/// See [the parent module](crate::impl_::freelist) for more details.
pub struct PyObjectFreeList {
entries: Box<[Option<NonNull<ffi::PyObject>>]>,
split: usize,
capacity: usize,
}
// safety: the pointers are never used internally and they are cleared when they are given out
unsafe impl Send for PyObjectFreeList {}
impl PyObjectFreeList {
/// Creates a new `PyObjectFreeList` instance with specified capacity.
pub fn with_capacity(capacity: usize) -> PyObjectFreeList {
let entries = vec![None; capacity].into_boxed_slice();
PyObjectFreeList {
entries,
split: 0,
capacity,
}
}
/// Pops the first non empty item.
pub fn pop(&mut self) -> Option<NonNull<ffi::PyObject>> {
let idx = self.split;
if idx == 0 {
None
} else {
let val = self.entries[idx - 1]
.take()
.expect("PyObjectFreeList is corrupt");
self.split = idx - 1;
Some(val)
}
}
/// Inserts a value into the list. Returns `Some(val)` if the `PyObjectFreeList` is full.
pub fn insert(&mut self, val: NonNull<ffi::PyObject>) -> Option<NonNull<ffi::PyObject>> {
let next = self.split + 1;
if next < self.capacity {
self.entries[self.split] = Some(val);
self.split = next;
None
} else {
Some(val)
}
}
}
+141
View File
@@ -0,0 +1,141 @@
use crate::types::any::PyAnyMethods;
use crate::Bound;
use crate::{exceptions::PyTypeError, FromPyObject, PyAny, PyErr, PyResult, Python};
#[cold]
pub fn failed_to_extract_enum(
py: Python<'_>,
type_name: &str,
variant_names: &[&str],
error_names: &[&str],
errors: &[PyErr],
) -> PyErr {
// TODO maybe use ExceptionGroup on Python 3.11+ ?
let mut err_msg = format!(
"failed to extract enum {} ('{}')",
type_name,
error_names.join(" | ")
);
for ((variant_name, error_name), error) in variant_names.iter().zip(error_names).zip(errors) {
use core::fmt::Write;
write!(
&mut err_msg,
"\n- variant {variant_name} ({error_name}): {error_msg}",
variant_name = variant_name,
error_name = error_name,
error_msg = extract_traceback(py, error.clone_ref(py)),
)
.unwrap();
}
PyTypeError::new_err(err_msg)
}
/// Flattens a chain of errors into a single string.
fn extract_traceback(py: Python<'_>, mut error: PyErr) -> String {
use core::fmt::Write;
let mut error_msg = error.to_string();
while let Some(cause) = error.cause(py) {
write!(&mut error_msg, ", caused by {cause}").unwrap();
error = cause
}
error_msg
}
pub fn extract_struct_field<'a, 'py, T>(
obj: &'a Bound<'py, PyAny>,
struct_name: &str,
field_name: &str,
) -> PyResult<T>
where
T: FromPyObject<'a, 'py>,
{
match obj.extract() {
Ok(value) => Ok(value),
Err(err) => Err(failed_to_extract_struct_field(
obj.py(),
err.into(),
struct_name,
field_name,
)),
}
}
pub fn extract_struct_field_with<'a, 'py, T>(
extractor: fn(&'a Bound<'py, PyAny>) -> PyResult<T>,
obj: &'a Bound<'py, PyAny>,
struct_name: &str,
field_name: &str,
) -> PyResult<T> {
match extractor(obj) {
Ok(value) => Ok(value),
Err(err) => Err(failed_to_extract_struct_field(
obj.py(),
err,
struct_name,
field_name,
)),
}
}
#[cold]
fn failed_to_extract_struct_field(
py: Python<'_>,
inner_err: PyErr,
struct_name: &str,
field_name: &str,
) -> PyErr {
let new_err = PyTypeError::new_err(format!(
"failed to extract field {struct_name}.{field_name}"
));
new_err.set_cause(py, ::core::option::Option::Some(inner_err));
new_err
}
pub fn extract_tuple_struct_field<'a, 'py, T>(
obj: &'a Bound<'py, PyAny>,
struct_name: &str,
index: usize,
) -> PyResult<T>
where
T: FromPyObject<'a, 'py>,
{
match obj.extract() {
Ok(value) => Ok(value),
Err(err) => Err(failed_to_extract_tuple_struct_field(
obj.py(),
err.into(),
struct_name,
index,
)),
}
}
pub fn extract_tuple_struct_field_with<'a, 'py, T>(
extractor: fn(&'a Bound<'py, PyAny>) -> PyResult<T>,
obj: &'a Bound<'py, PyAny>,
struct_name: &str,
index: usize,
) -> PyResult<T> {
match extractor(obj) {
Ok(value) => Ok(value),
Err(err) => Err(failed_to_extract_tuple_struct_field(
obj.py(),
err,
struct_name,
index,
)),
}
}
#[cold]
fn failed_to_extract_tuple_struct_field(
py: Python<'_>,
inner_err: PyErr,
struct_name: &str,
index: usize,
) -> PyErr {
let new_err = PyTypeError::new_err(format!("failed to extract field {struct_name}.{index}"));
new_err.set_cause(py, ::core::option::Option::Some(inner_err));
new_err
}
+150
View File
@@ -0,0 +1,150 @@
use crate::conversion::IntoPyObject;
use crate::inspect::PyStaticExpr;
/// Seals `PyReturnType` so that types outside PyO3 cannot implement it.
mod return_type {
use crate::{impl_::introspection::PyReturnType, IntoPyObject};
pub trait Sealed {}
impl<'a, T: IntoPyObject<'a>> Sealed for T {}
impl<T: PyReturnType, E> Sealed for Result<T, E> {}
}
/// Trait to guess a function Python return type
///
/// It is useful to properly get the return type `T` when the Rust implementation returns e.g. `PyResult<T>`
pub trait PyReturnType: return_type::Sealed {
/// The function return type
const OUTPUT_TYPE: PyStaticExpr;
}
impl<'a, T: IntoPyObject<'a>> PyReturnType for T {
const OUTPUT_TYPE: PyStaticExpr = T::OUTPUT_TYPE;
}
impl<T: PyReturnType, E> PyReturnType for Result<T, E> {
const OUTPUT_TYPE: PyStaticExpr = T::OUTPUT_TYPE;
}
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be converted to a Python object",
label = "required by `#[pyo3(get)]` to create a readable property from a field of type `{Self}`",
note = "implement `IntoPyObject` for `&{Self}` or `IntoPyObject + Clone` for `{Self}` to define the conversion"
)]
pub trait PyIntoPyObjectMaybeRefType<const REF_IMPL_EXISTS: bool> {
const OUTPUT_TYPE: PyStaticExpr;
}
impl<'a, 'py, T: 'a> PyIntoPyObjectMaybeRefType<true> for T
where
&'a T: IntoPyObject<'py>,
{
const OUTPUT_TYPE: PyStaticExpr = <&T as IntoPyObject<'_>>::OUTPUT_TYPE;
}
impl<'py, T: IntoPyObject<'py>> PyIntoPyObjectMaybeRefType<false> for T {
const OUTPUT_TYPE: PyStaticExpr = <T as IntoPyObject<'_>>::OUTPUT_TYPE;
}
#[repr(C)]
pub struct SerializedIntrospectionFragment<const LEN: usize> {
pub length: u32,
pub fragment: [u8; LEN],
}
/// Escapes a string to be valid JSON. Does not add quotes around it
///
/// Returns the number of written bytes
pub const fn escape_json_string(input: &str, output: &mut [u8]) -> usize {
let input = input.as_bytes();
let mut input_i = 0;
let mut output_i = 0;
while input_i < input.len() {
match input[input_i] {
b'\\' => {
output[output_i] = b'\\';
output_i += 1;
output[output_i] = b'\\';
output_i += 1;
}
b'"' => {
output[output_i] = b'\\';
output_i += 1;
output[output_i] = b'"';
output_i += 1;
}
0x08 => {
output[output_i] = b'\\';
output_i += 1;
output[output_i] = b'b';
output_i += 1;
}
0x0C => {
output[output_i] = b'\\';
output_i += 1;
output[output_i] = b'f';
output_i += 1;
}
b'\n' => {
output[output_i] = b'\\';
output_i += 1;
output[output_i] = b'n';
output_i += 1;
}
b'\r' => {
output[output_i] = b'\\';
output_i += 1;
output[output_i] = b'r';
output_i += 1;
}
b'\t' => {
output[output_i] = b'\\';
output_i += 1;
output[output_i] = b't';
output_i += 1;
}
c @ 0..32 => {
output[output_i] = b'\\';
output_i += 1;
output[output_i] = b'u';
output_i += 1;
output[output_i] = b'0';
output_i += 1;
output[output_i] = b'0';
output_i += 1;
output[output_i] = b'0' + (c / 16);
output_i += 1;
let remainder = c % 16;
output[output_i] = if remainder >= 10 {
b'a' + remainder - 10
} else {
b'0' + remainder
};
output_i += 1;
}
c => {
output[output_i] = c;
output_i += 1;
}
}
input_i += 1;
}
output_i
}
/// Number of bytes written by [`escape_json_string`]
pub const fn escaped_json_string_len(input: &str) -> usize {
let input = input.as_bytes();
let mut len = 0;
let mut i = 0;
while i < input.len() {
len += match input[i] {
b'\\' | b'"' | 0x08 | 0x0C | b'\n' | b'\r' | b'\t' => 2,
0..32 => 6,
_ => 1,
};
i += 1;
}
len
}
+27
View File
@@ -0,0 +1,27 @@
/// Type which will panic if dropped.
///
/// If this is dropped during a panic, this will cause an abort.
///
/// Use this to avoid letting unwinds cross through the FFI boundary, which is UB.
pub struct PanicTrap {
msg: &'static str,
}
impl PanicTrap {
#[inline]
pub const fn new(msg: &'static str) -> Self {
Self { msg }
}
#[inline]
pub const fn disarm(self) {
core::mem::forget(self)
}
}
impl Drop for PanicTrap {
fn drop(&mut self) {
// Panic here will abort the process, assuming in an unwind.
panic!("{}", self.msg)
}
}
+5
View File
@@ -0,0 +1,5 @@
//! Externally-accessible implementation of pycell
pub use crate::pycell::impl_::{
GetBorrowChecker, PyClassMutability, PyClassObjectBase, PyClassObjectBaseLayout,
PyStaticClassObject, PyVariableClassObject, PyVariableClassObjectBase,
};
+1673
View File
File diff suppressed because it is too large. Load diff
+46
View File
@@ -0,0 +1,46 @@
/// Helper function that can be used at compile time to emit a diagnostic if
/// the type does not implement `Send` or `Sync` when it should; the mere act
/// of invoking this function will cause the diagnostic to be emitted if needed.
pub const fn assert_pyclass_send_sync<T>()
where
T: Send + Sync,
{
}
#[track_caller]
#[allow(clippy::assertions_on_constants, reason = "invoked by a proc macro")]
pub const fn assert_dict_supported() {
assert!(
cfg!(any(not(Py_LIMITED_API), Py_3_9)),
"`dict` requires Python >= 3.9 when using the `abi3` feature"
);
}
#[track_caller]
#[allow(clippy::assertions_on_constants, reason = "invoked by a proc macro")]
pub const fn assert_weakref_supported() {
assert!(
cfg!(any(not(Py_LIMITED_API), Py_3_9)),
"`weakref` requires Python >= 3.9 when using the `abi3` feature"
)
}
#[track_caller]
#[allow(clippy::assertions_on_constants, reason = "invoked by a proc macro")]
pub const fn assert_immutable_type_supported() {
assert!(
cfg!(any(all(Py_3_10, not(Py_LIMITED_API)), Py_3_14)),
"`immutable_type` requires Python >= 3.10 (or >= 3.14 when using the `abi3` feature)"
);
}
mod tests {
#[cfg(feature = "macros")]
#[test]
fn test_assert_pyclass_send_sync() {
#[crate::pyclass(crate = "crate")]
struct MyClass {}
super::assert_pyclass_send_sync::<MyClass>();
}
}
+120
View File
@@ -0,0 +1,120 @@
use core::{ffi::CStr, marker::PhantomData};
use crate::{impl_::pyclass::PyClassImpl, PyClass};
/// Trait implemented by classes with a known text signature for instantiation.
///
/// This is implemented by the `#[pymethods]` macro when handling expansion for a
/// `#[new]` method.
pub trait PyClassNewTextSignature {
const TEXT_SIGNATURE: &'static str;
}
/// Type which uses specialization on impl blocks to facilitate generating the documentation for a
/// `#[pyclass]` type.
///
/// At the moment, this is only used to help lift the `TEXT_SIGNATURE` constant to compile time
/// providing a base case and a specialized implementation when the signature is known at compile time.
///
/// In the future when const eval is more advanced, it will probably be possible to format the whole
/// class docstring at compile time as part of this type instead of in macro expansion.
pub struct PyClassDocGenerator<
ClassT: PyClass,
// switch to determine if a signature for class instantiation is known
const HAS_NEW_TEXT_SIGNATURE: bool,
>(PhantomData<ClassT>);
impl<ClassT: PyClass + PyClassNewTextSignature> PyClassDocGenerator<ClassT, true> {
pub const DOC_PIECES: &'static [&'static [u8]] = &[
<ClassT as PyClass>::NAME.as_bytes(),
ClassT::TEXT_SIGNATURE.as_bytes(),
b"\n--\n\n",
<ClassT as PyClassImpl>::RAW_DOC.to_bytes_with_nul(),
];
}
impl<ClassT: PyClass> PyClassDocGenerator<ClassT, false> {
pub const DOC_PIECES: &'static [&'static [u8]] =
&[<ClassT as PyClassImpl>::RAW_DOC.to_bytes_with_nul()];
}
/// Casts bytes to a CStr, ensuring they are valid.
pub const fn doc_bytes_as_cstr(bytes: &'static [u8]) -> &'static ::core::ffi::CStr {
match CStr::from_bytes_with_nul(bytes) {
Ok(cstr) => cstr,
#[cfg(not(from_bytes_with_nul_error))] // MSRV 1.86
Err(_) => panic!("invalid pyclass doc"),
#[cfg(from_bytes_with_nul_error)]
// This case may happen if the user provides an invalid docstring
Err(core::ffi::FromBytesWithNulError::InteriorNul { .. }) => {
panic!("pyclass doc contains nul bytes")
}
// This case shouldn't happen using the macro machinery as long as `PyClassDocGenerator`
// uses the RAW_DOC as the final piece, which is nul terminated.
#[cfg(from_bytes_with_nul_error)]
Err(core::ffi::FromBytesWithNulError::NotNulTerminated) => {
panic!("pyclass doc expected to be nul terminated")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(feature = "macros")]
fn test_doc_generator() {
use crate::impl_::concat::{combine_to_array, combined_len};
/// A dummy class with signature.
#[crate::pyclass(crate = "crate")]
struct MyClass;
#[crate::pymethods(crate = "crate")]
impl MyClass {
#[new]
fn new(x: i32, y: i32) -> Self {
let _ = (x, y); // suppress unused variable warnings
MyClass
}
}
// simulate what the macro is doing
const PIECES: &[&[u8]] = PyClassDocGenerator::<MyClass, true>::DOC_PIECES;
assert_eq!(
&combine_to_array::<{ combined_len(PIECES) }>(PIECES),
b"MyClass(x, y)\n--\n\nA dummy class with signature.\0"
);
// simulate if the macro detected no text signature
const PIECES_WITHOUT_SIGNATURE: &[&[u8]] =
PyClassDocGenerator::<MyClass, false>::DOC_PIECES;
assert_eq!(
&combine_to_array::<{ combined_len(PIECES_WITHOUT_SIGNATURE) }>(
PIECES_WITHOUT_SIGNATURE
),
b"A dummy class with signature.\0"
);
}
#[test]
fn test_doc_bytes_as_cstr() {
let cstr = doc_bytes_as_cstr(b"MyClass\0");
assert_eq!(cstr, c"MyClass");
}
#[test]
#[cfg(from_bytes_with_nul_error)]
#[should_panic(expected = "pyclass doc contains nul bytes")]
fn test_doc_bytes_as_cstr_central_nul() {
doc_bytes_as_cstr(b"MyClass\0Foo");
}
#[test]
#[cfg(from_bytes_with_nul_error)]
#[should_panic(expected = "pyclass doc expected to be nul terminated")]
fn test_doc_bytes_as_cstr_not_nul_terminated() {
doc_bytes_as_cstr(b"MyClass");
}
}
+268
View File
@@ -0,0 +1,268 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
use core::{ffi::CStr, marker::PhantomData};
use std::thread::{self, ThreadId};
#[cfg(Py_3_14)]
use crate::err::error_on_minusone;
#[allow(deprecated)]
use crate::sync::GILOnceCell;
#[cfg(Py_3_14)]
use crate::types::PyTypeMethods;
use crate::{
exceptions::PyRuntimeError,
ffi,
impl_::pymethods::PyMethodDefType,
pyclass::{create_type_object, PyClassTypeObject},
types::PyType,
Bound, Py, PyAny, PyClass, PyErr, PyResult, Python,
};
use std::sync::Mutex;
use super::PyClassItemsIter;
/// Lazy type object for PyClass.
#[doc(hidden)]
pub struct LazyTypeObject<T>(LazyTypeObjectInner, PhantomData<T>);
// Non-generic inner of LazyTypeObject to keep code size down
struct LazyTypeObjectInner {
#[allow(deprecated)]
value: GILOnceCell<PyClassTypeObject>,
// Threads which have begun initialization of the `tp_dict`. Used for
// reentrant initialization detection.
initializing_threads: Mutex<Vec<ThreadId>>,
#[allow(deprecated)]
fully_initialized_type: GILOnceCell<Py<PyType>>,
}
impl<T> LazyTypeObject<T> {
/// Creates an uninitialized `LazyTypeObject`.
#[allow(clippy::new_without_default)]
pub const fn new() -> Self {
LazyTypeObject(
LazyTypeObjectInner {
#[allow(deprecated)]
value: GILOnceCell::new(),
initializing_threads: Mutex::new(Vec::new()),
#[allow(deprecated)]
fully_initialized_type: GILOnceCell::new(),
},
PhantomData,
)
}
}
impl<T: PyClass> LazyTypeObject<T> {
/// Gets the type object contained by this `LazyTypeObject`, initializing it if needed.
#[inline]
pub fn get_or_try_init<'py>(&self, py: Python<'py>) -> PyResult<&Bound<'py, PyType>> {
if let Some(type_object) = self.0.fully_initialized_type.get(py) {
// Fast path
return Ok(type_object.bind(py));
}
self.try_init(py)
}
#[cold]
fn try_init<'py>(&self, py: Python<'py>) -> PyResult<&Bound<'py, PyType>> {
self.0.get_or_try_init(
py,
create_type_object::<T>,
<T as PyClass>::NAME,
T::items_iter(),
)
}
}
impl LazyTypeObjectInner {
// Uses dynamically dispatched fn(Python<'py>) -> PyResult<Py<PyType>
// so that this code is only instantiated once, instead of for every T
// like the generic LazyTypeObject<T> methods above.
fn get_or_try_init<'py>(
&self,
py: Python<'py>,
init: fn(Python<'py>) -> PyResult<PyClassTypeObject>,
name: &str,
items_iter: PyClassItemsIter,
) -> PyResult<&Bound<'py, PyType>> {
(|| -> PyResult<_> {
let PyClassTypeObject {
type_object,
is_immutable_type,
..
} = self.value.get_or_try_init(py, || init(py))?;
let type_object = type_object.bind(py);
self.ensure_init(type_object, *is_immutable_type, name, items_iter)?;
Ok(type_object)
})()
.map_err(|err| {
wrap_in_runtime_error(
py,
err,
format!("An error occurred while initializing class {name}"),
)
})
}
fn ensure_init(
&self,
type_object: &Bound<'_, PyType>,
#[allow(unused_variables)] is_immutable_type: bool,
name: &str,
items_iter: PyClassItemsIter,
) -> PyResult<()> {
let py = type_object.py();
// We might want to fill the `tp_dict` with python instances of `T`
// itself. In order to do so, we must first initialize the type object
// with an empty `tp_dict`: now we can create instances of `T`.
//
// Then we fill the `tp_dict`. Multiple threads may try to fill it at
// the same time, but only one of them will succeed.
//
// More importantly, if a thread is performing initialization of the
// `tp_dict`, it can still request the type object through `get_or_init`,
// but the `tp_dict` may appear empty of course.
if self.fully_initialized_type.get(py).is_some() {
// `tp_dict` is already filled: ok.
return Ok(());
}
let thread_id = thread::current().id();
{
let mut threads = self.initializing_threads.lock().unwrap();
if threads.contains(&thread_id) {
// Reentrant call: just return the type object, even if the
// `tp_dict` is not filled yet.
return Ok(());
}
threads.push(thread_id);
}
struct InitializationGuard<'a> {
initializing_threads: &'a Mutex<Vec<ThreadId>>,
thread_id: ThreadId,
}
impl Drop for InitializationGuard<'_> {
fn drop(&mut self) {
let mut threads = self.initializing_threads.lock().unwrap();
threads.retain(|id| *id != self.thread_id);
}
}
let guard = InitializationGuard {
initializing_threads: &self.initializing_threads,
thread_id,
};
// Pre-compute the class attribute objects: this can temporarily
// release the GIL since we're calling into arbitrary user code. It
// means that another thread can continue the initialization in the
// meantime: at worst, we'll just make a useless computation.
let mut items = vec![];
for class_items in items_iter {
for method in class_items.methods {
if let PyMethodDefType::ClassAttribute(attr) = method {
match (attr.meth)(py) {
Ok(val) => items.push((attr.name, val)),
Err(err) => {
return Err(wrap_in_runtime_error(
py,
err,
format!(
"An error occurred while initializing `{}.{}`",
name,
attr.name.to_str().unwrap()
),
))
}
}
}
}
}
// Now we hold the GIL and we can assume it won't be released until we
// return from the function.
let result = self.fully_initialized_type.get_or_try_init(py, move || {
initialize_tp_dict(py, type_object.as_ptr(), items)?;
#[cfg(Py_3_14)]
if is_immutable_type {
// freeze immutable types after __dict__ is initialized
let res = unsafe { ffi::PyType_Freeze(type_object.as_type_ptr()) };
error_on_minusone(py, res)?;
}
#[cfg(all(Py_3_10, not(Py_LIMITED_API), not(Py_3_14)))]
if is_immutable_type {
use crate::types::PyTypeMethods as _;
#[cfg(not(Py_GIL_DISABLED))]
unsafe {
(*type_object.as_type_ptr()).tp_flags |= ffi::Py_TPFLAGS_IMMUTABLETYPE
};
#[cfg(Py_GIL_DISABLED)]
unsafe {
(*type_object.as_type_ptr()).tp_flags.fetch_or(
ffi::Py_TPFLAGS_IMMUTABLETYPE,
core::sync::atomic::Ordering::Relaxed,
)
};
unsafe { ffi::PyType_Modified(type_object.as_type_ptr()) };
}
// Initialization successfully complete, can clear the thread list.
// (No further calls to get_or_init() will try to init, on any thread.)
let mut threads = {
drop(guard);
self.initializing_threads.lock().unwrap()
};
threads.clear();
Ok(type_object.clone().unbind())
});
if let Err(err) = result {
return Err(wrap_in_runtime_error(
py,
err,
format!("An error occurred while initializing `{name}.__dict__`"),
));
}
Ok(())
}
}
fn initialize_tp_dict(
py: Python<'_>,
type_object: *mut ffi::PyObject,
items: Vec<(&'static CStr, Py<PyAny>)>,
) -> PyResult<()> {
// We hold the GIL: the dictionary update can be considered atomic from
// the POV of other threads.
for (key, val) in items {
crate::err::error_on_minusone(py, unsafe {
ffi::PyObject_SetAttrString(type_object, key.as_ptr(), val.as_ptr())
})?;
}
Ok(())
}
// This is necessary for making static `LazyTypeObject`s
unsafe impl<T> Sync for LazyTypeObject<T> {}
/// Used in the macro-expanded implementation of `type_object_raw` for `#[pyclass]` types
#[cold]
pub fn type_object_init_failed(py: Python<'_>, err: PyErr, type_name: &str) -> ! {
err.write_unraisable(py, None);
panic!("failed to create type object for `{type_name}`")
}
#[cold]
fn wrap_in_runtime_error(py: Python<'_>, err: PyErr, message: String) -> PyErr {
let runtime_err = PyRuntimeError::new_err(message);
runtime_err.set_cause(py, Some(err));
runtime_err
}
+100
View File
@@ -0,0 +1,100 @@
use core::marker::PhantomData;
use crate::conversion::IntoPyObject;
use crate::{FromPyObject, Py};
/// Trait used to combine with zero-sized types to calculate at compile time
/// some property of a type.
///
/// The trick uses the fact that an associated constant has higher priority
/// than a trait constant, so we can use the trait to define the false case.
///
/// The true case is defined in the zero-sized type's impl block, which is
/// gated on some property like trait bound or only being implemented
/// for fixed concrete types.
pub trait Probe: probe::Sealed {
const VALUE: bool = false;
}
/// Seals `Probe` so that types outside PyO3 cannot implement it.
mod probe {
pub trait Sealed {}
}
macro_rules! probe {
($name:ident) => {
pub struct $name<T>(PhantomData<T>);
impl<T> Probe for $name<T> {}
impl<T> probe::Sealed for $name<T> {}
};
}
probe!(IsPyT);
impl<T> IsPyT<Py<T>> {
pub const VALUE: bool = true;
}
probe!(IsIntoPyObjectRef);
impl<'a, 'py, T: 'a> IsIntoPyObjectRef<T>
where
&'a T: IntoPyObject<'py>,
{
pub const VALUE: bool = true;
}
probe!(IsSend);
impl<T: Send> IsSend<T> {
pub const VALUE: bool = true;
}
probe!(IsSync);
impl<T: Sync> IsSync<T> {
pub const VALUE: bool = true;
}
probe!(IsFromPyObject);
impl<'a, 'py, T> IsFromPyObject<T>
where
T: FromPyObject<'a, 'py>,
{
pub const VALUE: bool = true;
}
probe!(HasNewTextSignature);
impl<T: super::doc::PyClassNewTextSignature> HasNewTextSignature<T> {
pub const VALUE: bool = true;
}
probe!(IsClone);
impl<T: Clone> IsClone<T> {
pub const VALUE: bool = true;
}
probe!(IsReturningEmptyTuple);
impl IsReturningEmptyTuple<()> {
pub const VALUE: bool = true;
}
impl<E> IsReturningEmptyTuple<Result<(), E>> {
pub const VALUE: bool = true;
}
#[cfg(test)]
macro_rules! value_of {
($probe:ident, $ty:ty) => {{
#[allow(unused_imports)] // probe trait not used if VALUE is true
use crate::impl_::pyclass::Probe as _;
$probe::<$ty>::VALUE
}};
}
#[cfg(test)]
pub(crate) use value_of;
+2
View File
@@ -0,0 +1,2 @@
/// Re-exported so that macros can name this internal type.
pub use crate::internal::pyclass_init::PyNativeTypeInitializer;
+167
View File
@@ -0,0 +1,167 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
use core::cell::UnsafeCell;
use crate::{
ffi,
ffi_ptr_ext::FfiPtrExt,
py_result_ext::PyResultExt,
types::{PyCFunction, PyModule, PyModuleMethods},
Borrowed, Bound, PyResult, Python,
};
pub use crate::impl_::pymethods::PyMethodDef;
/// Wrapper around `ffi::PyMethodDef` suitable to use as a static variable for `#[pyfunction]` values.
///
/// The `UnsafeCell` is used because the Python interpreter consumes these as `*mut ffi::PyMethodDef`.
pub struct PyFunctionDef(UnsafeCell<ffi::PyMethodDef>);
// Safety: contents are only ever used by the Python interpreter, which uses global statics in this way.
unsafe impl Sync for PyFunctionDef {}
impl PyFunctionDef {
pub const fn new(def: ffi::PyMethodDef) -> Self {
Self(UnsafeCell::new(def))
}
pub const fn from_method_def(def: PyMethodDef) -> Self {
Self::new(def.into_raw())
}
pub(crate) fn create_py_c_function<'py>(
&'static self,
py: Python<'py>,
module: Option<&Bound<'py, PyModule>>,
) -> PyResult<Bound<'py, PyCFunction>> {
// Safety: self is static
unsafe { create_py_c_function(py, self.0.get(), module) }
}
}
/// Trait to enable the use of `wrap_pyfunction` with both `Python` and `PyModule`,
/// and also to infer the return type of either `&'py PyCFunction` or `Bound<'py, PyCFunction>`.
pub trait WrapPyFunctionArg<'py, T>: wrap_pyfunctionarg::Sealed {
fn wrap_pyfunction(self, function_def: &'static PyFunctionDef) -> PyResult<T>;
}
/// Seals `WrapPyFunctionArg` so that types outside PyO3 cannot implement it.
mod wrap_pyfunctionarg {
use crate::{types::PyModule, Borrowed, Bound, Python};
pub trait Sealed {}
impl<'py> Sealed for Bound<'py, PyModule> {}
impl<'py> Sealed for &Bound<'py, PyModule> {}
impl<'a, 'py> Sealed for Borrowed<'a, 'py, PyModule> {}
impl<'a, 'py> Sealed for &Borrowed<'a, 'py, PyModule> {}
impl<'py> Sealed for Python<'py> {}
}
impl<'py> WrapPyFunctionArg<'py, Bound<'py, PyCFunction>> for Bound<'py, PyModule> {
fn wrap_pyfunction(
self,
function_def: &'static PyFunctionDef,
) -> PyResult<Bound<'py, PyCFunction>> {
function_def.create_py_c_function(self.py(), Some(&self))
}
}
impl<'py> WrapPyFunctionArg<'py, Bound<'py, PyCFunction>> for &'_ Bound<'py, PyModule> {
fn wrap_pyfunction(
self,
function_def: &'static PyFunctionDef,
) -> PyResult<Bound<'py, PyCFunction>> {
function_def.create_py_c_function(self.py(), Some(self))
}
}
impl<'py> WrapPyFunctionArg<'py, Bound<'py, PyCFunction>> for Borrowed<'_, 'py, PyModule> {
fn wrap_pyfunction(
self,
function_def: &'static PyFunctionDef,
) -> PyResult<Bound<'py, PyCFunction>> {
function_def.create_py_c_function(self.py(), Some(&self))
}
}
impl<'py> WrapPyFunctionArg<'py, Bound<'py, PyCFunction>> for &'_ Borrowed<'_, 'py, PyModule> {
fn wrap_pyfunction(
self,
function_def: &'static PyFunctionDef,
) -> PyResult<Bound<'py, PyCFunction>> {
function_def.create_py_c_function(self.py(), Some(self))
}
}
impl<'py> WrapPyFunctionArg<'py, Bound<'py, PyCFunction>> for Python<'py> {
fn wrap_pyfunction(
self,
function_def: &'static PyFunctionDef,
) -> PyResult<Bound<'py, PyCFunction>> {
function_def.create_py_c_function(self, None)
}
}
/// Creates a `PyCFunction` object from a `PyMethodDef`.
///
/// # Safety
///
/// The `method_def` pointer must be valid for the lifetime of the returned `PyCFunction`
/// (effectively, it must be a static variable).
pub unsafe fn create_py_c_function<'py>(
py: Python<'py>,
method_def: *mut ffi::PyMethodDef,
module: Option<&Bound<'py, PyModule>>,
) -> PyResult<Bound<'py, PyCFunction>> {
let (mod_ptr, module_name) = if let Some(m) = module {
let mod_ptr = m.as_ptr();
(mod_ptr, Some(m.name()?))
} else {
(core::ptr::null_mut(), None)
};
let module_name_ptr = module_name
.as_ref()
.map_or(core::ptr::null_mut(), Bound::as_ptr);
unsafe {
ffi::PyCFunction_NewEx(method_def, mod_ptr, module_name_ptr)
.assume_owned_or_err(py)
.cast_into_unchecked()
}
}
#[cfg(test)]
#[cfg(feature = "macros")]
mod tests {
#[test]
fn test_wrap_pyfunction_forms() {
use crate::types::{PyAnyMethods, PyModule};
use crate::{wrap_pyfunction, Python};
#[crate::pyfunction(crate = "crate")]
fn f() {}
Python::attach(|py| {
let module = PyModule::new(py, "test_wrap_pyfunction_forms").unwrap();
let func = wrap_pyfunction!(f, module.clone()).unwrap();
func.call0().unwrap();
let func = wrap_pyfunction!(f, &module).unwrap();
func.call0().unwrap();
let module_borrowed = module.as_borrowed();
let func = wrap_pyfunction!(f, module_borrowed).unwrap();
func.call0().unwrap();
let func = wrap_pyfunction!(f, &module_borrowed).unwrap();
func.call0().unwrap();
let func = wrap_pyfunction!(f, py).unwrap();
func.call0().unwrap();
});
}
}
+834
View File
@@ -0,0 +1,834 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
use crate::exceptions::PyStopAsyncIteration;
use crate::impl_::callback::IntoPyCallbackOutput;
use crate::impl_::panic::PanicTrap;
use crate::impl_::pycell::PyClassObjectBaseLayout;
use crate::impl_::pyclass::PyClassDict as _;
use crate::internal::get_slot::{get_slot, TP_BASE, TP_CLEAR, TP_TRAVERSE};
use crate::internal::pyclass_init::PyClassInit;
use crate::internal::state::ForbidAttaching;
use crate::pycell::impl_::{PyClassBorrowChecker as _, PyClassObjectLayout};
use crate::types::PyType;
use crate::{
ffi, Borrowed, Bound, Py, PyAny, PyClass, PyErr, PyResult, PyTraverseError, PyVisit, Python,
};
use core::ffi::CStr;
use core::ffi::{c_int, c_void};
use core::fmt;
use core::marker::PhantomData;
use core::panic::AssertUnwindSafe;
use core::ptr::{null_mut, NonNull};
use std::panic::catch_unwind;
use super::pyclass::PyClassImpl;
use super::trampoline;
use crate::internal_tricks::{clear_eq, traverse_eq};
/// `PyMethodDefType` represents different types of Python callable objects.
/// It is used by the `#[pymethods]` attribute.
#[derive(Copy, Clone)]
pub enum PyMethodDefType {
/// Represents a class method (might be `classmethod` or `staticmethod`, depends on `ml_flags`)
Method(PyMethodDef),
/// Represents class attribute, used by `#[attribute]`
ClassAttribute(PyClassAttributeDef),
/// Represents getter descriptor, used by `#[getter]`
Getter(PyGetterDef),
/// Represents setter descriptor, used by `#[setter]` and `#[deleter]`
Setter(PySetterDef),
/// Represents deleter descriptor, used by `#[deleter]`
Deleter(PyDeleterDef),
/// Represents a struct member
StructMember(ffi::PyMemberDef),
}
#[derive(Copy, Clone, Debug)]
pub enum PyMethodType {
PyCFunction(ffi::PyCFunction),
PyCFunctionWithKeywords(ffi::PyCFunctionWithKeywords),
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
PyCFunctionFastWithKeywords(ffi::PyCFunctionFastWithKeywords),
}
pub type PyClassAttributeFactory = for<'p> fn(Python<'p>) -> PyResult<Py<PyAny>>;
#[derive(Copy, Clone, Debug)]
pub struct PyMethodDef {
pub(crate) ml_name: &'static CStr,
pub(crate) ml_meth: PyMethodType,
pub(crate) ml_flags: c_int,
pub(crate) ml_doc: &'static CStr,
}
#[derive(Copy, Clone)]
pub struct PyClassAttributeDef {
pub(crate) name: &'static CStr,
pub(crate) meth: PyClassAttributeFactory,
}
#[derive(Copy, Clone)]
pub struct PyGetterDef {
pub(crate) name: &'static CStr,
pub(crate) meth: Getter,
pub(crate) doc: Option<&'static CStr>,
}
#[derive(Copy, Clone)]
pub struct PySetterDef {
pub(crate) name: &'static CStr,
pub(crate) meth: Setter,
pub(crate) doc: Option<&'static CStr>,
}
#[derive(Copy, Clone)]
pub struct PyDeleterDef {
pub(crate) name: &'static CStr,
pub(crate) meth: Deleter,
pub(crate) doc: Option<&'static CStr>,
}
/// Abstraction around fastcall calling convention, which is only available in Python 3.10 and up,
/// can inline this directly into the proc macro when Python 3.10 support dropped
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
#[macro_export]
#[doc(hidden)]
macro_rules! maybe_define_fastcall_function_with_keywords {
($name:ident, $py:ident, $slf:ident, $args:ident, $nargs:ident, $kwargs:ident, $body:block) => {
#[allow(non_snake_case)]
unsafe fn $name<'py>(
$py: $crate::Python<'py>,
$slf: *mut $crate::ffi::PyObject,
$args: *const *mut $crate::ffi::PyObject,
$nargs: $crate::ffi::Py_ssize_t,
$kwargs: *mut $crate::ffi::PyObject
) -> $crate::PyResult<*mut $crate::ffi::PyObject> $body
};
}
/// On older abi3 versions, required to use varargs calling convention
#[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
#[macro_export]
#[doc(hidden)]
macro_rules! maybe_define_fastcall_function_with_keywords {
($name:ident, $py:ident, $slf:ident, $args:ident, $nargs:ident, $kwargs:ident, $body:block) => {
#[allow(non_snake_case)]
unsafe fn $name<'py>(
$py: $crate::Python<'py>,
$slf: *mut $crate::ffi::PyObject,
$args: *mut $crate::ffi::PyObject,
$kwargs: *mut $crate::ffi::PyObject
) -> $crate::PyResult<*mut $crate::ffi::PyObject> $body
};
}
pub use crate::maybe_define_fastcall_function_with_keywords;
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
#[macro_export]
#[doc(hidden)]
macro_rules! maybe_extract_arguments_fastcall {
($description:ident, $py:ident, $args:ident, $nargs:ident, $kwargs:ident, $args_array:ident, $args_handler:ty, $kwargs_handler:ty) => {
// SAFETY: guaranteed by the proc macro that all args to extract_arguments_fastcall are valid
unsafe {
$description.extract_arguments_fastcall::<$args_handler, $kwargs_handler>(
$py,
$args,
$nargs,
$kwargs,
&mut $args_array,
)
}
};
}
#[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
#[macro_export]
#[doc(hidden)]
macro_rules! maybe_extract_arguments_fastcall {
($description:ident, $py:ident, $args:ident, $nargs:ident, $kwargs:ident, $args_array:ident, $args_handler:ty, $kwargs_handler:ty) => {
// SAFETY: guaranteed by the proc macro that all args to extract_arguments_tuple_dict are valid
unsafe {
$description.extract_arguments_tuple_dict::<$args_handler, $kwargs_handler>(
$py,
$args,
$kwargs,
&mut $args_array,
)
}
};
}
pub use crate::maybe_extract_arguments_fastcall;
impl PyMethodDef {
/// Define a function that takes no arguments.
pub const fn noargs(
ml_name: &'static CStr,
cfunction: ffi::PyCFunction,
ml_doc: &'static CStr,
) -> Self {
Self {
ml_name,
ml_meth: PyMethodType::PyCFunction(cfunction),
ml_flags: ffi::METH_NOARGS,
ml_doc,
}
}
/// Define a function that takes arbitrary arguments as a tuple and dict.
pub const fn cfunction_with_keywords(
ml_name: &'static CStr,
cfunction: ffi::PyCFunctionWithKeywords,
ml_doc: &'static CStr,
) -> Self {
Self {
ml_name,
ml_meth: PyMethodType::PyCFunctionWithKeywords(cfunction),
ml_flags: ffi::METH_VARARGS | ffi::METH_KEYWORDS,
ml_doc,
}
}
/// Define a function that takes arbitrary arguments as a C-style array and tuple of keyword arguments.
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
pub const fn fastcall_cfunction_with_keywords(
ml_name: &'static CStr,
cfunction: ffi::PyCFunctionFastWithKeywords,
ml_doc: &'static CStr,
) -> Self {
Self {
ml_name,
ml_meth: PyMethodType::PyCFunctionFastWithKeywords(cfunction),
ml_flags: ffi::METH_FASTCALL | ffi::METH_KEYWORDS,
ml_doc,
}
}
/// Abstraction over fastcall to fall back to varargs on older Python versions.
pub const fn maybe_fastcall_cfunction_with_keywords(
ml_name: &'static CStr,
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))] cfunction: ffi::PyCFunctionFastWithKeywords,
// on older abi3 versions, Fastcall not supported
#[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))] cfunction: ffi::PyCFunctionWithKeywords,
ml_doc: &'static CStr,
) -> Self {
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
{
Self::fastcall_cfunction_with_keywords(ml_name, cfunction, ml_doc)
}
#[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
{
Self::cfunction_with_keywords(ml_name, cfunction, ml_doc)
}
}
pub const fn flags(mut self, flags: c_int) -> Self {
self.ml_flags |= flags;
self
}
pub const fn into_raw(self) -> ffi::PyMethodDef {
let meth = match self.ml_meth {
PyMethodType::PyCFunction(meth) => ffi::PyMethodDefPointer { PyCFunction: meth },
PyMethodType::PyCFunctionWithKeywords(meth) => ffi::PyMethodDefPointer {
PyCFunctionWithKeywords: meth,
},
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
PyMethodType::PyCFunctionFastWithKeywords(meth) => ffi::PyMethodDefPointer {
PyCFunctionFastWithKeywords: meth,
},
};
ffi::PyMethodDef {
ml_name: self.ml_name.as_ptr(),
ml_meth: meth,
ml_flags: self.ml_flags,
ml_doc: self.ml_doc.as_ptr(),
}
}
}
impl PyClassAttributeDef {
/// Define a class attribute.
pub const fn new(name: &'static CStr, meth: PyClassAttributeFactory) -> Self {
Self { name, meth }
}
}
// Manual implementation because `Python<'_>` does not implement `Debug` and
// trait bounds on `fn` compiler-generated derive impls are too restrictive.
impl fmt::Debug for PyClassAttributeDef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PyClassAttributeDef")
.field("name", &self.name)
.finish()
}
}
/// Class getter / setters
pub(crate) type Getter =
for<'py> unsafe fn(Python<'py>, NonNull<ffi::PyObject>) -> PyResult<*mut ffi::PyObject>;
pub(crate) type Setter = for<'py> unsafe fn(
Python<'py>,
NonNull<ffi::PyObject>,
NonNull<ffi::PyObject>,
) -> PyResult<c_int>;
pub(crate) type Deleter =
for<'py> unsafe fn(Python<'py>, NonNull<ffi::PyObject>) -> PyResult<c_int>;
impl PyGetterDef {
/// Define a getter.
pub const fn new(name: &'static CStr, getter: Getter, doc: Option<&'static CStr>) -> Self {
Self {
name,
meth: getter,
doc,
}
}
}
impl PySetterDef {
/// Define a setter.
pub const fn new(name: &'static CStr, setter: Setter, doc: Option<&'static CStr>) -> Self {
Self {
name,
meth: setter,
doc,
}
}
}
impl PyDeleterDef {
/// Define a deleter.
pub const fn new(name: &'static CStr, deleter: Deleter, doc: Option<&'static CStr>) -> Self {
Self {
name,
meth: deleter,
doc,
}
}
}
/// Calls an implementation of __traverse__ for tp_traverse
///
/// NB cannot accept `'static` visitor, this is a sanity check below:
///
/// ```rust,compile_fail
/// use pyo3::prelude::*;
/// use pyo3::pyclass::{PyTraverseError, PyVisit};
///
/// #[pyclass]
/// struct Foo;
///
/// #[pymethods]
/// impl Foo {
/// fn __traverse__(&self, _visit: PyVisit<'static>) -> Result<(), PyTraverseError> {
/// Ok(())
/// }
/// }
/// ```
///
/// Elided lifetime should compile ok:
///
/// ```rust,no_run
/// use pyo3::prelude::*;
/// use pyo3::pyclass::{PyTraverseError, PyVisit};
///
/// #[pyclass]
/// struct Foo;
///
/// #[pymethods]
/// impl Foo {
/// fn __traverse__(&self, _visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
/// Ok(())
/// }
/// }
/// ```
#[doc(hidden)]
pub unsafe fn _call_traverse<T>(
slf: *mut ffi::PyObject,
impl_: fn(&T, PyVisit<'_>) -> Result<(), PyTraverseError>,
visit: ffi::visitproc,
arg: *mut c_void,
current_traverse: ffi::traverseproc,
) -> c_int
where
T: PyClass,
{
// It is important the implementation of `__traverse__` cannot safely access the interpreter,
// c.f. https://github.com/PyO3/pyo3/issues/3165, and hence we do not expose our Python
// token to the user code and forbid safe methods for attaching.
// (This includes enforcing the `&self` method receiver as e.g. `PyRef<Self>` could
// reconstruct a Python token via `PyRef::py`.)
//
// The traversal lives in `traverse_impl` so that it can return early: `trap` is armed until
// `disarm` below, and dropping it armed panics, which aborts out of `tp_traverse`.
let trap = PanicTrap::new("uncaught panic inside __traverse__ handler");
let lock = ForbidAttaching::during_traverse();
let retval = unsafe { traverse_impl(slf, impl_, visit, arg, current_traverse) };
// Drop lock before trap just in case dropping lock panics
drop(lock);
trap.disarm();
retval
}
/// Visits the base type, the instance `__dict__` and the pyclass's own data, stopping as soon as
/// one of them returns non-zero.
///
/// # Safety
/// - `slf` must be a valid pointer to an instance of `T`.
/// - Must only be called from `_call_traverse`, which holds the `PanicTrap` and `ForbidAttaching`
/// lock this relies on.
unsafe fn traverse_impl<T>(
slf: *mut ffi::PyObject,
impl_: fn(&T, PyVisit<'_>) -> Result<(), PyTraverseError>,
visit: ffi::visitproc,
arg: *mut c_void,
current_traverse: ffi::traverseproc,
) -> c_int
where
T: PyClass,
{
// A non-zero return means a `visitproc` has asked us to stop the traversal.
let super_retval = unsafe { call_super_traverse(slf, visit, arg, current_traverse) };
if super_retval != 0 {
return super_retval;
}
// SAFETY: `slf` is a valid Python object pointer to a class object of type T, and
// traversal is running so no mutations can occur.
let class_object: &<T as PyClassImpl>::Layout = unsafe { &*slf.cast() };
// The `__dict__` is not Rust data, so it is visited without the thread and borrow checks
// below: it must stay reachable to the GC even when the pyclass data cannot be traversed.
let dict_retval = unsafe { class_object.contents().dict.traverse_dict(visit, arg) };
if dict_retval != 0 {
return dict_retval;
}
// `#[pyclass(unsendable)]` types can only be deallocated by their own thread, so do not
// traverse them if not on their owning thread :(
// ... and we cannot traverse a type which might be being mutated by a Rust thread.
if class_object.check_threadsafe().is_err()
|| class_object.borrow_checker().try_borrow().is_err()
{
return 0;
}
struct TraverseGuard<'a, T: PyClassImpl>(&'a T::Layout);
impl<T: PyClassImpl> Drop for TraverseGuard<'_, T> {
fn drop(&mut self) {
self.0.borrow_checker().release_borrow()
}
}
// `.try_borrow()` above created a borrow, we need to release it when we're done
// traversing the object. This allows us to read `instance` safely.
let _guard = TraverseGuard::<T>(class_object);
let instance = unsafe { &*class_object.contents().value.get() };
let visit = PyVisit {
visit,
arg,
_guard: PhantomData,
};
match catch_unwind(AssertUnwindSafe(move || impl_(instance, visit))) {
Ok(Ok(())) => 0,
Ok(Err(traverse_error)) => traverse_error.into_inner(),
Err(_err) => -1,
}
}
/// Call super-type traverse method, if necessary.
///
/// Adapted from <https://github.com/cython/cython/blob/7acfb375fb54a033f021b0982a3cd40c34fb22ac/Cython/Utility/ExtensionTypes.c#L386>
///
/// TODO: There are possible optimizations over looking up the base type in this way
/// - if the base type is known in this module, can potentially look it up directly in module state
/// (when we have it)
/// - if the base type is a Python builtin, can jut call the C function directly
/// - if the base type is a PyO3 type defined in the same module, can potentially do similar to
/// tp_alloc where we solve this at compile time
unsafe fn call_super_traverse(
obj: *mut ffi::PyObject,
visit: ffi::visitproc,
arg: *mut c_void,
current_traverse: ffi::traverseproc,
) -> c_int {
// SAFETY: in this function here it's ok to work with raw type objects `ffi::Py_TYPE`
// because the GC is running and so
// - (a) we cannot do refcounting and
// - (b) the type of the object cannot change.
let mut ty = unsafe { ffi::Py_TYPE(obj) };
let mut traverse: Option<ffi::traverseproc>;
// First find the current type by the current_traverse function
loop {
traverse = unsafe { get_slot(ty, TP_TRAVERSE) };
if traverse_eq(traverse, current_traverse) {
break;
}
ty = unsafe { get_slot(ty, TP_BASE) };
if ty.is_null() {
// FIXME: return an error if current type not in the MRO? Should be impossible.
return 0;
}
}
// Get first base which has a different traverse function
while traverse_eq(traverse, current_traverse) {
ty = unsafe { get_slot(ty, TP_BASE) };
if ty.is_null() {
break;
}
traverse = unsafe { get_slot(ty, TP_TRAVERSE) };
}
// If we found a type with a different traverse function, call it
if let Some(traverse) = traverse {
return unsafe { traverse(obj, visit, arg) };
}
// FIXME same question as cython: what if the current type is not in the MRO?
0
}
/// Calls an implementation of __clear__ for tp_clear
pub unsafe fn _call_clear<T>(
slf: *mut ffi::PyObject,
impl_: for<'py> unsafe fn(Python<'py>, *mut ffi::PyObject) -> PyResult<()>,
current_clear: ffi::inquiry,
) -> c_int
where
T: PyClass,
{
unsafe {
trampoline::trampoline(move |py| {
let super_retval = call_super_clear(py, slf, current_clear);
if super_retval != 0 {
return Err(PyErr::fetch(py));
}
impl_(py, slf)?;
// Clear the `__dict__`, breaking any reference cycle through the instance's
// attributes.
//
// SAFETY: `slf` is a valid instance of `T`. A shared reference suffices: clearing
// the `__dict__` never touches the pyclass data, so needs no borrow check.
let class_object: &<T as PyClassImpl>::Layout = &*slf.cast();
class_object.contents().dict.clear_dict(py);
Ok(0)
})
}
}
/// `tp_traverse` for a `#[pyclass]` which defines no `__traverse__` of its own: visits the
/// base type and the instance `__dict__` (if it is a `#[pyclass(dict)]`).
pub unsafe extern "C" fn synthesized_traverse<T>(
slf: *mut ffi::PyObject,
visit: ffi::visitproc,
arg: *mut c_void,
) -> c_int
where
T: PyClass,
{
let super_retval = unsafe { call_super_traverse(slf, visit, arg, synthesized_traverse::<T>) };
if super_retval != 0 {
return super_retval;
}
// SAFETY: `slf` is a valid pointer to an instance of `T`, and traversal is running so no
// mutations can occur. The `__dict__` is not Rust data, so needs no thread or borrow check.
let class_object: &<T as PyClassImpl>::Layout = unsafe { &*slf.cast() };
unsafe { class_object.contents().dict.traverse_dict(visit, arg) }
}
/// `tp_clear` for a `#[pyclass]` which defines no `__clear__` of its own: calls the base type
/// and clears the instance `__dict__` (if it is a `#[pyclass(dict)]`).
pub unsafe extern "C" fn synthesized_clear<T>(slf: *mut ffi::PyObject) -> c_int
where
T: PyClass,
{
unsafe { _call_clear::<T>(slf, |_, _| Ok(()), synthesized_clear::<T>) }
}
/// Call super-type traverse method, if necessary.
///
/// Adapted from <https://github.com/cython/cython/blob/7acfb375fb54a033f021b0982a3cd40c34fb22ac/Cython/Utility/ExtensionTypes.c#L386>
///
/// TODO: There are possible optimizations over looking up the base type in this way
/// - if the base type is known in this module, can potentially look it up directly in module state
/// (when we have it)
/// - if the base type is a Python builtin, can jut call the C function directly
/// - if the base type is a PyO3 type defined in the same module, can potentially do similar to
/// tp_alloc where we solve this at compile time
unsafe fn call_super_clear(
py: Python<'_>,
obj: *mut ffi::PyObject,
current_clear: ffi::inquiry,
) -> c_int {
let mut ty = unsafe { PyType::from_borrowed_type_ptr(py, ffi::Py_TYPE(obj)) };
let mut clear: Option<ffi::inquiry>;
// First find the current type by the current_clear function
loop {
clear = ty.get_slot(TP_CLEAR);
if clear_eq(clear, current_clear) {
break;
}
let base = ty.get_slot(TP_BASE);
if base.is_null() {
// FIXME: return an error if current type not in the MRO? Should be impossible.
return 0;
}
ty = unsafe { PyType::from_borrowed_type_ptr(py, base) };
}
// Get first base which has a different clear function
while clear_eq(clear, current_clear) {
let base = ty.get_slot(TP_BASE);
if base.is_null() {
break;
}
ty = unsafe { PyType::from_borrowed_type_ptr(py, base) };
clear = ty.get_slot(TP_CLEAR);
}
// If we found a type with a different clear function, call it
if let Some(clear) = clear {
return unsafe { clear(obj) };
}
// FIXME same question as cython: what if the current type is not in the MRO?
0
}
// Autoref-based specialization for handling `__next__` returning `Option`
pub struct IterBaseTag;
impl IterBaseTag {
#[inline]
pub fn convert<'py, Value, Target>(self, py: Python<'py>, value: Value) -> PyResult<Target>
where
Value: IntoPyCallbackOutput<'py, Target>,
{
value.convert(py)
}
}
pub trait IterBaseKind {
#[inline]
fn iter_tag(&self) -> IterBaseTag {
IterBaseTag
}
}
impl<Value> IterBaseKind for &Value {}
pub struct IterOptionTag;
impl IterOptionTag {
#[inline]
pub fn convert<'py, Value>(
self,
py: Python<'py>,
value: Option<Value>,
) -> PyResult<*mut ffi::PyObject>
where
Value: IntoPyCallbackOutput<'py, *mut ffi::PyObject>,
{
match value {
Some(value) => value.convert(py),
None => Ok(null_mut()),
}
}
}
pub trait IterOptionKind {
#[inline]
fn iter_tag(&self) -> IterOptionTag {
IterOptionTag
}
}
impl<Value> IterOptionKind for Option<Value> {}
pub struct IterResultOptionTag;
impl IterResultOptionTag {
#[inline]
pub fn convert<'py, Value, Error>(
self,
py: Python<'py>,
value: Result<Option<Value>, Error>,
) -> PyResult<*mut ffi::PyObject>
where
Value: IntoPyCallbackOutput<'py, *mut ffi::PyObject>,
Error: Into<PyErr>,
{
match value {
Ok(Some(value)) => value.convert(py),
Ok(None) => Ok(null_mut()),
Err(err) => Err(err.into()),
}
}
}
pub trait IterResultOptionKind {
#[inline]
fn iter_tag(&self) -> IterResultOptionTag {
IterResultOptionTag
}
}
impl<Value, Error> IterResultOptionKind for Result<Option<Value>, Error> {}
// Autoref-based specialization for handling `__anext__` returning `Option`
pub struct AsyncIterBaseTag;
impl AsyncIterBaseTag {
#[inline]
pub fn convert<'py, Value, Target>(self, py: Python<'py>, value: Value) -> PyResult<Target>
where
Value: IntoPyCallbackOutput<'py, Target>,
{
value.convert(py)
}
}
pub trait AsyncIterBaseKind {
#[inline]
fn async_iter_tag(&self) -> AsyncIterBaseTag {
AsyncIterBaseTag
}
}
impl<Value> AsyncIterBaseKind for &Value {}
pub struct AsyncIterOptionTag;
impl AsyncIterOptionTag {
#[inline]
pub fn convert<'py, Value>(
self,
py: Python<'py>,
value: Option<Value>,
) -> PyResult<*mut ffi::PyObject>
where
Value: IntoPyCallbackOutput<'py, *mut ffi::PyObject>,
{
match value {
Some(value) => value.convert(py),
None => Err(PyStopAsyncIteration::new_err(())),
}
}
}
pub trait AsyncIterOptionKind {
#[inline]
fn async_iter_tag(&self) -> AsyncIterOptionTag {
AsyncIterOptionTag
}
}
impl<Value> AsyncIterOptionKind for Option<Value> {}
pub struct AsyncIterResultOptionTag;
impl AsyncIterResultOptionTag {
#[inline]
pub fn convert<'py, Value, Error>(
self,
py: Python<'py>,
value: Result<Option<Value>, Error>,
) -> PyResult<*mut ffi::PyObject>
where
Value: IntoPyCallbackOutput<'py, *mut ffi::PyObject>,
Error: Into<PyErr>,
{
match value {
Ok(Some(value)) => value.convert(py),
Ok(None) => Err(PyStopAsyncIteration::new_err(())),
Err(err) => Err(err.into()),
}
}
}
pub trait AsyncIterResultOptionKind {
#[inline]
fn async_iter_tag(&self) -> AsyncIterResultOptionTag {
AsyncIterResultOptionTag
}
}
impl<Value, Error> AsyncIterResultOptionKind for Result<Option<Value>, Error> {}
/// Re-exported so that `#[new]` generated code can resolve the type tag for `tp_new_impl`
pub use crate::internal::pyclass_init::tp_new_resolver;
#[expect(
private_bounds,
reason = "`PyClassInit` is not a public trait, bound exist for diagnostics"
)]
/// # SAFETY
/// - `cls` must be the type object for `ClassT` (or a subclass)
pub unsafe fn tp_new_impl<'py, InitializerT, ClassT>(
py: Python<'py>,
initializer: InitializerT,
cls: *mut ffi::PyTypeObject,
) -> PyResult<*mut ffi::PyObject>
where
InitializerT: PyClassInit<'py, ClassT>,
{
// SAFETY: caller has guaranteed `cls` is the correct object
unsafe { initializer.init(Borrowed::from_ptr_unchecked(py, cls.cast()).cast_unchecked()) }
.map(Bound::into_ptr)
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
fn test_fastcall_function_with_keywords() {
use super::PyMethodDef;
use crate::impl_::pyfunction::PyFunctionDef;
use crate::types::PyAnyMethods;
use crate::{ffi, Python};
Python::attach(|py| {
let def =
PyFunctionDef::from_method_def(PyMethodDef::fastcall_cfunction_with_keywords(
c"test",
accepts_no_arguments,
c"doc",
));
// leak to make it 'static
// deliberately done at runtime to have coverage of `PyFunctionDef::from_method_def`
let def = Box::leak(Box::new(def));
unsafe extern "C" fn accepts_no_arguments(
_slf: *mut ffi::PyObject,
_args: *const *mut ffi::PyObject,
nargs: ffi::Py_ssize_t,
kwargs: *mut ffi::PyObject,
) -> *mut ffi::PyObject {
assert_eq!(nargs, 0);
assert!(kwargs.is_null());
unsafe { Python::assume_attached().None().into_ptr() }
}
let f = def.create_py_c_function(py, None).unwrap();
f.call0().unwrap();
});
}
}
+742
View File
@@ -0,0 +1,742 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
//! Implementation details of `#[pymodule]` which need to be accessible from proc-macro generated code.
use core::{
cell::UnsafeCell,
ffi::CStr,
ffi::{c_int, c_void},
marker::PhantomData,
};
#[cfg(all(
not(any(PyPy, GraalPy)),
Py_3_9,
not(all(windows, Py_LIMITED_API, not(Py_3_10))),
))]
use core::sync::atomic::Ordering;
#[cfg(all(
not(any(PyPy, GraalPy)),
Py_3_9,
not(all(windows, Py_LIMITED_API, not(Py_3_10))),
target_has_atomic = "64",
))]
use core::sync::atomic::AtomicI64;
#[cfg(all(
not(any(PyPy, GraalPy)),
Py_3_9,
not(all(windows, Py_LIMITED_API, not(Py_3_10))),
not(target_has_atomic = "64"),
))]
use portable_atomic::AtomicI64;
#[cfg(not(any(PyPy, GraalPy)))]
use crate::exceptions::PyImportError;
#[cfg(any(not(all(Py_LIMITED_API, Py_GIL_DISABLED)), Py_3_15))]
use crate::internal_tricks::array_ptr_as_mut;
use crate::prelude::PyTypeMethods;
use crate::{
ffi,
impl_::pyfunction::PyFunctionDef,
types::{PyModule, PyModuleMethods},
Bound, PyClass, PyResult, PyTypeInfo,
};
use crate::{ffi_ptr_ext::FfiPtrExt, PyErr};
use crate::{
sync::PyOnceLock,
types::{any::PyAnyMethods, dict::PyDictMethods, PyDict},
Py, PyAny, Python,
};
/// `Sync` wrapper of `ffi::PyModuleDef`.
pub struct ModuleDef {
// wrapped in UnsafeCell so that Rust compiler treats this as interior mutability
#[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
ffi_def: UnsafeCell<ffi::PyModuleDef>,
#[cfg(Py_3_15)]
name: &'static CStr,
#[cfg(Py_3_15)]
doc: &'static CStr,
#[cfg(Py_3_15)]
slots: &'static PyModuleSlots,
/// Interpreter ID where module was initialized (not applicable on PyPy).
#[cfg(all(
not(any(PyPy, GraalPy)),
Py_3_9,
not(all(windows, Py_LIMITED_API, not(Py_3_10)))
))]
interpreter: AtomicI64,
/// Initialized module object, cached to avoid reinitialization.
module: PyOnceLock<Py<PyModule>>,
}
unsafe impl Sync for ModuleDef {}
impl ModuleDef {
/// Make new module definition with given module name.
pub const fn new(
name: &'static CStr,
doc: &'static CStr,
slots: &'static PrimaryModuleSlots,
secondary_slots: &'static SecondaryModuleSlots,
) -> Self {
// This is only used in PyO3 for append_to_inittab on Python 3.15 and newer.
// There could also be other tools that need the legacy init hook.
#[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
#[allow(clippy::declare_interior_mutable_const)]
const INIT: ffi::PyModuleDef = ffi::PyModuleDef {
m_base: ffi::PyModuleDef_HEAD_INIT,
m_name: core::ptr::null(),
m_doc: core::ptr::null(),
m_size: 0,
m_methods: core::ptr::null_mut(),
m_slots: core::ptr::null_mut(),
m_traverse: None,
m_clear: None,
m_free: None,
};
#[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
let ffi_def = UnsafeCell::new(ffi::PyModuleDef {
m_name: name.as_ptr(),
m_doc: doc.as_ptr(),
m_slots: array_ptr_as_mut({
cfg_select! {
Py_3_15 => secondary_slots.0.get(),
_ => slots.0.get(),
}
}),
..INIT
});
#[cfg(any(not(Py_3_15), all(Py_LIMITED_API, Py_GIL_DISABLED)))]
let _ = secondary_slots;
ModuleDef {
#[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
ffi_def,
#[cfg(Py_3_15)]
name,
#[cfg(Py_3_15)]
doc,
#[cfg(Py_3_15)]
slots,
// -1 is never expected to be a valid interpreter ID
#[cfg(all(
not(any(PyPy, GraalPy)),
Py_3_9,
not(all(windows, Py_LIMITED_API, not(Py_3_10)))
))]
interpreter: AtomicI64::new(-1),
module: PyOnceLock::new(),
}
}
#[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
pub fn init_multi_phase(&'static self) -> *mut ffi::PyObject {
unsafe { ffi::PyModuleDef_Init(self.ffi_def.get()) }
}
/// Builds a module object directly. Used for [`#[pymodule]`][crate::pymodule] submodules.
pub fn make_module(&'static self, py: Python<'_>) -> PyResult<Py<PyModule>> {
// Check the interpreter ID has not changed, since we currently have no way to guarantee
// that static data is not reused across interpreters.
//
// PyPy does not have subinterpreters, so no need to check interpreter ID.
//
// TODO: it should be possible to use the Py_mod_multiple_interpreters slot on sufficiently
// new Python versions to remove the need for this custom logic
#[cfg(not(any(PyPy, GraalPy)))]
{
// PyInterpreterState_Get is only available on 3.9 and later, but is missing
// from python3.dll for Windows stable API on 3.9
#[cfg(all(Py_3_9, not(all(windows, Py_LIMITED_API, not(Py_3_10)))))]
{
let current_interpreter =
unsafe { ffi::PyInterpreterState_GetID(ffi::PyInterpreterState_Get()) };
crate::err::error_on_minusone(py, current_interpreter)?;
if let Err(initialized_interpreter) = self.interpreter.compare_exchange(
-1,
current_interpreter,
Ordering::SeqCst,
Ordering::SeqCst,
) {
if initialized_interpreter != current_interpreter {
return Err(PyImportError::new_err(
"PyO3 modules do not yet support subinterpreters, see https://github.com/PyO3/pyo3/issues/576",
));
}
}
}
#[cfg(not(all(Py_3_9, not(all(windows, Py_LIMITED_API, not(Py_3_10))))))]
{
// CPython before 3.9 does not have APIs to check the interpreter ID, so best that can be
// done to guard against subinterpreters is fail if the module is initialized twice
if self.module.get(py).is_some() {
return Err(PyImportError::new_err(
"PyO3 modules compiled for CPython 3.8 or older may only be initialized once per interpreter process"
));
}
}
}
// Make a dummy spec, needs a `name` attribute and that seems to be sufficient
// for the loader system
static SIMPLE_NAMESPACE: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
let simple_ns = SIMPLE_NAMESPACE.import(py, "types", "SimpleNamespace")?;
#[cfg(not(Py_3_15))]
{
let ffi_def = self.ffi_def.get();
let m_name = unsafe { CStr::from_ptr((*ffi_def).m_name) };
let name = m_name
.to_str()
.map_err(|e| {
crate::exceptions::PyUnicodeDecodeError::new_err_from_utf8(
py,
m_name.to_bytes(),
e,
)
})?
.to_string();
let kwargs = PyDict::new(py);
kwargs.set_item("name", name)?;
let spec = simple_ns.call((), Some(&kwargs))?;
self.module
.get_or_try_init(py, || {
let def = self.ffi_def.get();
let module = unsafe {
ffi::PyModule_FromDefAndSpec(def, spec.as_ptr()).assume_owned_or_err(py)?
}
.cast_into()?;
if unsafe { ffi::PyModule_ExecDef(module.as_ptr(), def) } != 0 {
return Err(PyErr::fetch(py));
}
Ok(module.unbind())
})
.map(|py_module| py_module.clone_ref(py))
}
#[cfg(Py_3_15)]
{
let name = self.name;
let doc = self.doc;
let kwargs = PyDict::new(py);
kwargs.set_item("name", name)?;
let spec = simple_ns.call((), Some(&kwargs))?;
self.module
.get_or_try_init(py, || {
let slots = self.get_slots();
let module = unsafe {
ffi::PyModule_FromSlotsAndSpec(slots, spec.as_ptr())
.assume_owned_or_err(py)?
}
.cast_into()?;
if unsafe { ffi::PyModule_SetDocString(module.as_ptr(), doc.as_ptr()) } != 0 {
return Err(PyErr::fetch(py));
}
if unsafe { ffi::PyModule_Exec(module.as_ptr()) } != 0 {
return Err(PyErr::fetch(py));
}
Ok(module.unbind())
})
.map(|py_module| py_module.clone_ref(py))
}
}
#[cfg(Py_3_15)]
pub fn get_slots(&'static self) -> *mut ffi::PySlot {
array_ptr_as_mut(self.slots.0.get())
}
}
/// Defines the `PyModExport_<name>` entry point used by Python 3.15 and newer.
///
/// This is wrapped in a `macro_rules!` so the proc-macro backend can emit a single
/// version-agnostic invocation; the body only expands on Python 3.15+, where
/// `ffi::PySlot` is defined.
#[cfg(Py_3_15)]
#[doc(hidden)]
#[macro_export]
macro_rules! __pyo3_pymodexport {
($symbol:literal, $def:path) => {
#[doc(hidden)]
#[export_name = $symbol]
pub unsafe extern "C" fn __pyo3_export() -> *mut $crate::ffi::PySlot {
$def.get_slots()
}
};
}
#[cfg(not(Py_3_15))]
#[doc(hidden)]
#[macro_export]
macro_rules! __pyo3_pymodexport {
($symbol:literal, $def:path) => {};
}
/// Defines the `PyInit_<name>` entry point used by Python 3.14 and older.
///
/// This is wrapped in a `macro_rules!` so the proc-macro backend can emit a single
/// version-agnostic invocation; the body only expands on Python 3.14 and older
#[cfg(not(all(Py_3_15, Py_LIMITED_API, Py_GIL_DISABLED)))]
#[doc(hidden)]
#[macro_export]
macro_rules! __pyo3_pyinit {
($symbol:literal, $def:path) => {
#[doc(hidden)]
#[export_name = $symbol]
pub unsafe extern "C" fn __pyo3_init() -> *mut $crate::ffi::PyObject {
$def.init_multi_phase()
}
};
}
#[cfg(all(Py_3_15, Py_LIMITED_API, Py_GIL_DISABLED))]
#[doc(hidden)]
#[macro_export]
macro_rules! __pyo3_pyinit {
($symbol:literal, $def:path) => {};
}
/// Type of the exec slot used to initialise module contents
pub type ModuleExecSlot = unsafe extern "C" fn(*mut ffi::PyObject) -> c_int;
const MAX_SLOTS: usize =
// Py_mod_exec
1 +
// Py_mod_gil
cfg!(Py_3_13) as usize +
// Py_mod_name, Py_mod_doc, and Py_mod_abi
3 * (cfg!(Py_3_15) as usize);
const MAX_SLOTS_WITH_TRAILING_NULL: usize = MAX_SLOTS + 1;
/// On Python 3.15+ we use `PySlot` system and `PyModule_FromSlotsAndSpec`
#[cfg(Py_3_15)]
pub type PrimaryModuleSlots = PyModuleSlots;
#[cfg(all(Py_3_15, not(all(Py_LIMITED_API, Py_GIL_DISABLED))))]
pub type SecondaryModuleSlots = PyModuleDefSlots;
/// On Python 3.14 and older the primary system is `ffi::PyModuleDef`.
#[cfg(not(Py_3_15))]
pub type PrimaryModuleSlots = PyModuleDefSlots;
#[cfg(not(all(Py_3_15, not(all(Py_LIMITED_API, Py_GIL_DISABLED)))))]
pub type SecondaryModuleSlots = ();
pub const fn secondary_slots(slots: &'static PrimaryModuleSlots) -> SecondaryModuleSlots {
cfg_select! {
// On Python 3.15+ we populate `PyModuleDefSlots` to point at primary slots
// (as long as not using abi3t where `PyModuleDef` is opaque and we cannot know the layout)
all(Py_3_15, not(all(Py_LIMITED_API, Py_GIL_DISABLED))) => PyModuleDefSlots(UnsafeCell::new([
ffi::PyModuleDef_Slot {
slot: ffi::Py_slot_subslots,
value: slots.0.get().cast(),
},
// SAFETY: terminator of C-style array
unsafe { core::mem::zeroed() },
])),
// Older versions have no secondary slots
_ => { let _ = slots; }
}
}
/// Builder to create module slots. The size of the number of slots desired must
/// be known up front, and N needs to be at least one greater than the number of
/// actual slots pushed due to the need to have a zeroed element on the end.
pub struct PyModuleSlotsBuilder {
// values (initially all zeroed)
slots: PrimaryModuleSlots,
// current length
len: usize,
}
// note that macros cannot use conditional compilation,
// so all implementations below must be available in all
// Python versions
// By handling it here we can avoid conditional
// compilation within the macros; they can always emit
// e.g. a `.with_gil_used()` call.
impl PyModuleSlotsBuilder {
#[allow(clippy::new_without_default)]
pub const fn new() -> Self {
Self {
slots: cfg_select! {
Py_3_15 => PyModuleSlots(UnsafeCell::new(
// SAFETY: `PySlot` is legal to be zeroed (terminates C-style array)
[unsafe { core::mem::zeroed::<ffi::PySlot>() }; MAX_SLOTS_WITH_TRAILING_NULL],
)),
_ => PyModuleDefSlots(UnsafeCell::new(
// SAFETY: `PyModuleDef_Slot` is legal to be zeroed (terminates C-style array)
[unsafe { core::mem::zeroed::<ffi::PyModuleDef_Slot>() }; MAX_SLOTS_WITH_TRAILING_NULL],
))
},
len: 0,
}
}
pub const fn with_mod_exec(self, exec: ModuleExecSlot) -> Self {
#[cfg(not(Py_3_15))]
{
self.push(ffi::Py_mod_exec, exec as *mut c_void)
}
#[cfg(Py_3_15)]
{
// safety: exce is not NULL
self.push_value(unsafe { ffi::PySlot_FUNC(ffi::Py_mod_exec, exec as *mut c_void) })
}
}
pub const fn with_gil_used(self, gil_used: bool) -> Self {
#[cfg(all(Py_3_13, not(Py_3_15)))]
{
self.push(
ffi::Py_mod_gil,
if gil_used {
ffi::Py_MOD_GIL_USED
} else {
ffi::Py_MOD_GIL_NOT_USED
},
)
}
#[cfg(Py_3_15)]
{
self.push_value(ffi::PySlot_DATA(
ffi::Py_mod_gil,
if gil_used {
ffi::Py_MOD_GIL_USED
} else {
ffi::Py_MOD_GIL_NOT_USED
},
))
}
#[cfg(not(Py_3_13))]
{
// Silence unused variable warning
let _ = gil_used;
self
}
}
pub const fn with_name(self, name: &'static CStr) -> Self {
#[cfg(Py_3_15)]
{
self.push_value(ffi::PySlot_STATIC_DATA(
ffi::Py_mod_name,
name.as_ptr() as *mut c_void,
))
}
#[cfg(not(Py_3_15))]
{
// Silence unused variable warning
let _ = name;
self
}
}
pub const fn with_abi_info(self) -> Self {
#[cfg(Py_3_15)]
{
ffi::PyABIInfo_VAR!(ABI_INFO);
self.push_value(ffi::PySlot_STATIC_DATA(
ffi::Py_mod_abi,
(&raw mut ABI_INFO).cast(),
))
}
#[cfg(not(Py_3_15))]
{
self
}
}
pub const fn with_doc(self, doc: &'static CStr) -> Self {
#[cfg(Py_3_15)]
{
self.push_value(ffi::PySlot_STATIC_DATA(
ffi::Py_mod_doc,
doc.as_ptr() as *mut c_void,
))
}
#[cfg(not(Py_3_15))]
{
// Silence unused variable warning
let _ = doc;
self
}
}
pub const fn build(self) -> PrimaryModuleSlots {
self.slots
}
#[cfg(not(Py_3_15))]
const fn push(mut self, slot: c_int, value: *mut c_void) -> Self {
// Required to guarantee there's still a zeroed element
// at the end
assert!(
self.len < MAX_SLOTS,
"Cannot add more than MAX_SLOTS slots to a PyModuleSlots",
);
self.slots.0.get_mut()[self.len] = ffi::PyModuleDef_Slot { slot, value };
self.len += 1;
self
}
#[cfg(Py_3_15)]
const fn push_value(mut self, value: ffi::PySlot) -> Self {
assert!(
self.len < MAX_SLOTS,
"Cannot add more than MAX_SLOTS slots to a PyModuleSlots",
);
self.slots.0.get_mut()[self.len] = value;
self.len += 1;
self
}
}
/// Wrapper to safely store module slots, to be used in a `ModuleDef`.
pub struct PyModuleSlots(
// necessarily empty before Python 3.15; PySlot doesn't exist
#[cfg(Py_3_15)] UnsafeCell<[ffi::PySlot; MAX_SLOTS_WITH_TRAILING_NULL]>,
);
/// Slots to populate a `PyModuleDef`
/// Cannot create a `PyModuleDef` on abi3t due to lack of knowledge of object layout
#[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
pub struct PyModuleDefSlots(
UnsafeCell<
[ffi::PyModuleDef_Slot; cfg_select! {
// on Python 3.15+ only one slot for pointing at the primary slots, plus trailing null
Py_3_15 => 2,
_ => MAX_SLOTS_WITH_TRAILING_NULL
}],
>,
);
// It might be possible to avoid this with SyncUnsafeCell in the future
//
// SAFETY: the inner values are only accessed within a `ModuleDef`,
// used to call `PyModule_FromSlotsAndSpec`
unsafe impl Sync for PyModuleSlots {}
// SAFETY: the inner values are only accessed within a `ModuleDef`,
// which only uses them to build the `ffi::ModuleDef`.
#[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
unsafe impl Sync for PyModuleDefSlots {}
/// Trait to add an element (class, function...) to a module.
///
/// Currently only implemented for classes.
pub trait PyAddToModule: crate::sealed::Sealed {
fn add_to_module(&'static self, module: &Bound<'_, PyModule>) -> PyResult<()>;
}
/// For adding native types (non-pyclass) to a module.
pub struct AddTypeToModule<T>(PhantomData<T>);
impl<T> AddTypeToModule<T> {
#[allow(clippy::new_without_default)]
pub const fn new() -> Self {
AddTypeToModule(PhantomData)
}
}
impl<T: PyTypeInfo> PyAddToModule for AddTypeToModule<T> {
fn add_to_module(&'static self, module: &Bound<'_, PyModule>) -> PyResult<()> {
let object = T::type_object(module.py());
module.add(object.name()?, object)
}
}
/// For adding a class to a module.
pub struct AddClassToModule<T>(PhantomData<T>);
impl<T> AddClassToModule<T> {
#[allow(clippy::new_without_default)]
pub const fn new() -> Self {
AddClassToModule(PhantomData)
}
}
impl<T: PyClass> PyAddToModule for AddClassToModule<T> {
fn add_to_module(&'static self, module: &Bound<'_, PyModule>) -> PyResult<()> {
module.add_class::<T>()
}
}
/// For adding a function to a module.
impl PyAddToModule for PyFunctionDef {
fn add_to_module(&'static self, module: &Bound<'_, PyModule>) -> PyResult<()> {
// safety: self is static
module.add_function(self.create_py_c_function(module.py(), Some(module))?)
}
}
/// For adding a module to a module.
impl PyAddToModule for ModuleDef {
fn add_to_module(&'static self, module: &Bound<'_, PyModule>) -> PyResult<()> {
module.add_submodule(self.make_module(module.py())?.bind(module.py()))
}
}
#[cfg(test)]
mod tests {
use alloc::borrow::Cow;
use core::{ffi::c_int, ffi::CStr};
use crate::impl_::trampoline;
use super::*;
unsafe extern "C" fn module_exec(_module: *mut ffi::PyObject) -> c_int {
0
}
#[test]
fn module_init() {
unsafe extern "C" fn module_exec(module: *mut ffi::PyObject) -> c_int {
unsafe {
trampoline::module_exec(module, |m| {
m.add("SOME_CONSTANT", 42)?;
Ok(())
})
}
}
static NAME: &CStr = c"test_module";
static DOC: &CStr = c"some doc";
static SLOTS: PrimaryModuleSlots = PyModuleSlotsBuilder::new()
.with_mod_exec(module_exec)
.with_gil_used(false)
.with_abi_info()
.with_name(NAME)
.with_doc(DOC)
.build();
static SECONDARY_SLOTS: SecondaryModuleSlots = secondary_slots(&SLOTS);
static MODULE_DEF: ModuleDef = ModuleDef::new(NAME, DOC, &SLOTS, &SECONDARY_SLOTS);
Python::attach(|py| {
let module = MODULE_DEF.make_module(py).unwrap().into_bound(py);
assert_eq!(
module
.getattr("__name__")
.unwrap()
.extract::<Cow<'_, str>>()
.unwrap(),
"test_module",
);
assert_eq!(
module
.getattr("__doc__")
.unwrap()
.extract::<Cow<'_, str>>()
.unwrap(),
"some doc",
);
assert_eq!(
module
.getattr("SOME_CONSTANT")
.unwrap()
.extract::<u8>()
.unwrap(),
42,
);
})
}
#[test]
fn module_def_new() {
// To get coverage for ModuleDef::new() need to create a non-static ModuleDef, however init
// etc require static ModuleDef, so this test needs to be separated out.
static NAME: &CStr = c"test_module";
static DOC: &CStr = c"some doc";
static SLOTS: PrimaryModuleSlots = PyModuleSlotsBuilder::new().build();
static SECONDARY_SLOTS: SecondaryModuleSlots = secondary_slots(&SLOTS);
let module_def: ModuleDef = ModuleDef::new(NAME, DOC, &SLOTS, &SECONDARY_SLOTS);
#[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
unsafe {
let expected_slots = cfg_select! {
Py_3_15 => SECONDARY_SLOTS.0.get().cast(),
_ => SLOTS.0.get().cast(),
};
assert_eq!((*module_def.ffi_def.get()).m_slots, expected_slots);
}
#[cfg(all(Py_3_15, not(all(Py_LIMITED_API, Py_GIL_DISABLED))))]
unsafe {
let secondary_slots = &*SECONDARY_SLOTS.0.get();
assert_eq!(secondary_slots[0].slot, ffi::Py_slot_subslots);
assert_eq!(secondary_slots[0].value, SLOTS.0.get().cast());
assert!(secondary_slots[1] == ffi::PyModuleDef_Slot::default());
}
#[cfg(Py_3_15)]
{
assert_eq!(module_def.name, NAME);
assert_eq!(module_def.doc, DOC);
}
}
#[test]
#[cfg(panic = "unwind")]
fn test_build_maximal_slots() {
let mut builder = PyModuleSlotsBuilder::new()
.with_mod_exec(module_exec)
.with_name(c"test_module")
.with_doc(c"some doc")
.with_gil_used(false)
.with_abi_info();
#[cfg(Py_3_15)]
{
let second_last = builder.slots.0.get_mut()[builder.len - 1];
let last = builder.slots.0.get_mut()[builder.len];
let zeroed = unsafe { core::mem::zeroed() };
fn raw_bytes(inst: &ffi::PySlot) -> &[u8] {
unsafe {
core::slice::from_raw_parts(
inst as *const ffi::PySlot as *const u8,
core::mem::size_of::<ffi::PySlot>(),
)
}
}
let zeroed_bytes = raw_bytes(&zeroed);
assert_eq!(raw_bytes(&last), zeroed_bytes);
assert_ne!(raw_bytes(&second_last), zeroed_bytes);
}
#[cfg(not(Py_3_15))]
{
let second_last = builder.slots.0.get_mut()[builder.len - 1];
let last = builder.slots.0.get_mut()[builder.len];
let zeroed = ffi::PyModuleDef_Slot::default();
assert!(last == zeroed);
assert!(second_last != zeroed);
}
assert!(builder.len == MAX_SLOTS);
let result = std::panic::catch_unwind(|| builder.with_mod_exec(module_exec).build());
assert!(result.is_err());
}
#[test]
#[should_panic]
fn test_module_slots_builder_overflow() {
let mut builder = PyModuleSlotsBuilder::new();
for _ in 0..MAX_SLOTS + 1 {
builder = builder.with_mod_exec(module_exec);
}
}
}
+355
View File
@@ -0,0 +1,355 @@
#![warn(clippy::undocumented_unsafe_blocks, clippy::missing_safety_doc)]
//! Trampolines for various pyfunction and pymethod implementations.
//!
//! They exist to monomorphise std::panic::catch_unwind once into PyO3, rather than inline in every
//! function, thus saving a huge amount of compile-time complexity.
use core::{any::Any, ffi::c_int, panic::UnwindSafe};
use crate::internal::state::AttachGuard;
use crate::{
ffi, ffi_ptr_ext::FfiPtrExt, impl_::callback::PyCallbackOutput, impl_::panic::PanicTrap,
panic::PanicException, types::PyModule, Bound, PyResult, Python,
};
#[inline]
pub unsafe fn module_exec(
module: *mut ffi::PyObject,
f: for<'a, 'py> fn(&'a Bound<'py, PyModule>) -> PyResult<()>,
) -> c_int {
// SAFETY: f accepts a Bound object so Python is attached
unsafe {
trampoline(|py| {
let module = module.assume_borrowed_or_err(py)?.cast::<PyModule>()?;
f(&module)?;
Ok(0)
})
}
}
/// A workaround for Rust not allowing function pointers as const generics: define a trait which
/// has a constant function pointer.
pub trait MethodDef<T> {
const METH: T;
}
/// Generates an implementation of `MethodDef` and then returns the trampoline function
/// specialized to call the provided method.
///
/// Note that the functions returned by this macro are instantiations of generic functions. Code
/// should not depend on these function pointers being stable (e.g. across compilation units);
/// the intended purpose of these is to create function pointers which can be passed to the Python
/// C-API to correctly wrap Rust functions.
#[macro_export]
#[doc(hidden)]
macro_rules! get_trampoline_function {
($trampoline:ident, $f:path) => {{
struct Def;
impl $crate::impl_::trampoline::MethodDef<$crate::impl_::trampoline::$trampoline::Func> for Def {
const METH: $crate::impl_::trampoline::$trampoline::Func = $f;
}
$crate::impl_::trampoline::$trampoline::<Def>
}};
}
pub use get_trampoline_function;
/// Macro to define a trampoline function for a given function signature.
///
/// This macro generates:
/// 1. An external "C" function that serves as the trampoline, generic on a specific function pointer.
/// 2. A companion module containing a non-generic inner function, and the function pointer type.
macro_rules! trampoline {
(pub fn $name:ident($($arg_names:ident: $arg_types:ty),* $(,)?) -> $ret:ty;) => {
/// External symbol called by Python, which calls the provided Rust function.
///
/// The Rust function is supplied via the generic parameter `Meth`.
///
/// # Safety
///
/// The interpreter must be attached
pub unsafe extern "C" fn $name<Meth: MethodDef<$name::Func>>(
$($arg_names: $arg_types,)*
) -> $ret {
// SAFETY: caller upholds requirements
unsafe { $name::inner($($arg_names),*, Meth::METH) }
}
/// Companion module contains the function pointer type.
pub mod $name {
use super::*;
/// Non-generic inner function to ensure only one trampoline instantiated
///
/// # Safety
///
/// The interpreter must be attached
#[inline]
pub(crate) unsafe fn inner($($arg_names: $arg_types),*, f: $name::Func) -> $ret {
// SAFETY: caller upholds requirements
unsafe { trampoline(|py| f(py, $($arg_names,)*)) }
}
/// The type of the function pointer for this trampoline.
pub type Func = for<'py> unsafe fn (Python<'py>, $($arg_types),*) -> PyResult<$ret>;
}
}
}
/// Noargs is a special case where the `_args` parameter is unused and not passed to the inner `Func`.
/// # Safety
///
/// Interpreter must be attached
pub unsafe extern "C" fn noargs<Meth: MethodDef<noargs::Func>>(
slf: *mut ffi::PyObject,
_args: *mut ffi::PyObject, // unused and value not defined
) -> *mut ffi::PyObject {
// SAFETY: caller upholds requirements
unsafe { noargs::inner(slf, Meth::METH) }
}
pub mod noargs {
use super::*;
/// # Safety
///
/// Interpreter must be attached
#[inline]
pub(crate) unsafe fn inner(slf: *mut ffi::PyObject, f: Func) -> *mut ffi::PyObject {
// SAFETY: caller upholds requirements
unsafe { trampoline(|py| f(py, slf)) }
}
pub type Func = unsafe fn(Python<'_>, *mut ffi::PyObject) -> PyResult<*mut ffi::PyObject>;
}
macro_rules! trampolines {
($(pub fn $name:ident($($arg_names:ident: $arg_types:ty),* $(,)?) -> $ret:ty);* ;) => {
$(trampoline!(pub fn $name($($arg_names: $arg_types),*) -> $ret;));*;
}
}
// Trampolines used by `PyMethodDef` constructors
trampolines!(
pub fn fastcall_cfunction_with_keywords(
slf: *mut ffi::PyObject,
args: *const *mut ffi::PyObject,
nargs: ffi::Py_ssize_t,
kwnames: *mut ffi::PyObject,
) -> *mut ffi::PyObject;
pub fn cfunction_with_keywords(
slf: *mut ffi::PyObject,
args: *mut ffi::PyObject,
kwargs: *mut ffi::PyObject,
) -> *mut ffi::PyObject;
);
/// "fastcall" method calls only available on abi3 in Python 3.10 and up, otherwise fall back to the older call convention.
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
pub use self::fastcall_cfunction_with_keywords as maybe_fastcall_cfunction_with_keywords;
#[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
pub use self::cfunction_with_keywords as maybe_fastcall_cfunction_with_keywords;
// Trampolines used by slot methods
trampolines!(
pub fn getattrofunc(slf: *mut ffi::PyObject, attr: *mut ffi::PyObject) -> *mut ffi::PyObject;
pub fn setattrofunc(
slf: *mut ffi::PyObject,
attr: *mut ffi::PyObject,
value: *mut ffi::PyObject,
) -> c_int;
pub fn binaryfunc(slf: *mut ffi::PyObject, arg1: *mut ffi::PyObject) -> *mut ffi::PyObject;
pub fn descrgetfunc(
slf: *mut ffi::PyObject,
arg1: *mut ffi::PyObject,
arg2: *mut ffi::PyObject,
) -> *mut ffi::PyObject;
pub fn getiterfunc(slf: *mut ffi::PyObject) -> *mut ffi::PyObject;
pub fn hashfunc(slf: *mut ffi::PyObject) -> ffi::Py_hash_t;
pub fn inquiry(slf: *mut ffi::PyObject) -> c_int;
pub fn iternextfunc(slf: *mut ffi::PyObject) -> *mut ffi::PyObject;
pub fn lenfunc(slf: *mut ffi::PyObject) -> ffi::Py_ssize_t;
pub fn newfunc(
subtype: *mut ffi::PyTypeObject,
args: *mut ffi::PyObject,
kwargs: *mut ffi::PyObject,
) -> *mut ffi::PyObject;
pub fn initproc(
slf: *mut ffi::PyObject,
args: *mut ffi::PyObject,
kwargs: *mut ffi::PyObject,
) -> c_int;
pub fn objobjproc(slf: *mut ffi::PyObject, arg1: *mut ffi::PyObject) -> c_int;
pub fn reprfunc(slf: *mut ffi::PyObject) -> *mut ffi::PyObject;
pub fn richcmpfunc(
slf: *mut ffi::PyObject,
other: *mut ffi::PyObject,
op: c_int,
) -> *mut ffi::PyObject;
pub fn ssizeargfunc(arg1: *mut ffi::PyObject, arg2: ffi::Py_ssize_t) -> *mut ffi::PyObject;
pub fn ternaryfunc(
slf: *mut ffi::PyObject,
arg1: *mut ffi::PyObject,
arg2: *mut ffi::PyObject,
) -> *mut ffi::PyObject;
pub fn unaryfunc(slf: *mut ffi::PyObject) -> *mut ffi::PyObject;
);
#[cfg(any(not(Py_LIMITED_API), Py_3_11))]
trampoline! {
pub fn getbufferproc(slf: *mut ffi::PyObject, buf: *mut ffi::Py_buffer, flags: c_int) -> c_int;
}
/// Releasebufferproc is a special case where the function cannot return an error,
/// so we use trampoline_unraisable.
/// # Safety
///
/// - It must be sound to call the method with slf and buf
/// - Interpreter must be attached
#[cfg(any(not(Py_LIMITED_API), Py_3_11))]
pub unsafe extern "C" fn releasebufferproc<Meth: MethodDef<releasebufferproc::Func>>(
slf: *mut ffi::PyObject,
buf: *mut ffi::Py_buffer,
) {
// SAFETY: caller upholds requirements
unsafe { releasebufferproc::inner(slf, buf, Meth::METH) }
}
#[cfg(any(not(Py_LIMITED_API), Py_3_11))]
pub mod releasebufferproc {
use super::*;
/// # Safety
///
/// - It must be sound to call f with slf and buf
/// - Interpreter must be attached
#[inline]
pub(crate) unsafe fn inner(slf: *mut ffi::PyObject, buf: *mut ffi::Py_buffer, f: Func) {
// SAFETY: caller upholds requirements
unsafe { trampoline_unraisable(|py| f(py, slf, buf), slf) }
}
pub type Func = unsafe fn(Python<'_>, *mut ffi::PyObject, *mut ffi::Py_buffer) -> PyResult<()>;
}
/// # Safety
///
/// - slf must be either a valid ffi::PyObject or NULL
/// - if slf isn't NULL it must not be used again
/// - The thread must be attached to the interpreter when this is called.
#[inline]
pub(crate) unsafe fn dealloc(
slf: *mut ffi::PyObject,
f: for<'py> unsafe fn(Python<'py>, *mut ffi::PyObject) -> (),
) {
// After calling tp_dealloc the object is no longer valid,
// so pass null_mut() to the context.
//
// (Note that we don't allow the implementation `f` to fail.)
//
// SAFETY: caller upholds requirements
unsafe {
trampoline_unraisable(
|py| {
f(py, slf);
Ok(())
},
core::ptr::null_mut(),
)
}
}
/// Implementation of trampoline functions, which sets up an AttachGuard and calls F.
///
/// Panics during execution are trapped so that they don't propagate through any
/// outer FFI boundary.
///
/// # Safety
/// The thread must already be attached to the interpreter when this is called.
#[inline]
pub(crate) unsafe fn trampoline<F, R>(body: F) -> R
where
F: for<'py> FnOnce(Python<'py>) -> PyResult<R> + UnwindSafe,
R: PyCallbackOutput,
{
let trap = PanicTrap::new("uncaught panic at ffi boundary");
// SAFETY: This function requires the thread to already be attached.
let guard = unsafe { AttachGuard::assume() };
let py = guard.python();
let out = panic_result_into_callback_output(
py,
std::panic::catch_unwind(move || -> PyResult<_> { body(py) }),
);
trap.disarm();
out
}
/// Converts the output of std::panic::catch_unwind into a Python function output, either by raising a Python
/// exception or by unwrapping the contained success output.
#[inline]
fn panic_result_into_callback_output<R>(
py: Python<'_>,
panic_result: Result<PyResult<R>, Box<dyn Any + Send + 'static>>,
) -> R
where
R: PyCallbackOutput,
{
let py_err = match panic_result {
Ok(Ok(value)) => return value,
Ok(Err(py_err)) => py_err,
Err(payload) => PanicException::from_panic_payload(payload),
};
py_err.restore(py);
R::ERR_VALUE
}
/// Implementation of trampoline for functions which can't return an error.
///
/// Panics during execution are trapped so that they don't propagate through any
/// outer FFI boundary.
///
/// Exceptions produced are sent to `sys.unraisablehook`.
///
/// # Safety
///
/// - ctx must be either a valid ffi::PyObject or NULL
/// - The thread must be attached to the interpreter when this is called.
#[inline]
unsafe fn trampoline_unraisable<F>(body: F, ctx: *mut ffi::PyObject)
where
F: for<'py> FnOnce(Python<'py>) -> PyResult<()> + UnwindSafe,
{
let trap = PanicTrap::new("uncaught panic at ffi boundary");
// SAFETY: Thread is known to be attached.
let guard = unsafe { AttachGuard::assume() };
let py = guard.python();
if let Err(py_err) = std::panic::catch_unwind(move || body(py))
.unwrap_or_else(|payload| Err(PanicException::from_panic_payload(payload)))
{
// SAFETY: caller upholds requirements
py_err.write_unraisable(py, unsafe { ctx.assume_borrowed_or_opt(py) }.as_deref());
}
trap.disarm();
}
+252
View File
@@ -0,0 +1,252 @@
#![warn(clippy::undocumented_unsafe_blocks)]
use crate::impl_::concat::slice_copy_from_slice;
/// This is a reimplementation of the `indoc` crate's unindent functionality:
///
/// 1. Count the leading spaces of each line, ignoring the first line and any lines that are empty or contain spaces only.
/// 2. Take the minimum.
/// 3. If the first line is empty i.e. the string begins with a newline, remove the first line.
/// 4. Remove the computed number of spaces from the beginning of each line.
const fn unindent_bytes(bytes: &mut [u8]) -> usize {
// (1) + (2) - count leading spaces, take the minimum
let Some(to_unindent) = get_minimum_leading_spaces(bytes) else {
// all lines were empty, nothing to unindent
return bytes.len();
};
// now copy from the original buffer, bringing values forward as needed
let mut read_idx = 0;
let mut write_idx = 0;
// (3) - remove first line if it is empty
match consume_eol(bytes, read_idx) {
// skip empty first line
Some(eol) => read_idx = eol,
// copy non-empty first line as-is
None => {
(read_idx, write_idx) = copy_forward_until_eol(bytes, read_idx, write_idx);
}
};
// (4) - unindent remaining lines
while read_idx < bytes.len() {
let leading_spaces = count_spaces(bytes, read_idx);
if leading_spaces < to_unindent {
read_idx += leading_spaces;
assert!(
consume_eol(bytes, read_idx).is_some(),
"removed fewer spaces than expected on non-empty line"
);
} else {
// leading_spaces may be equal to or larger than to_unindent, only need to unindent
// the required amount, additional indentation is meaningful
read_idx += to_unindent;
}
// copy remainder of line
(read_idx, write_idx) = copy_forward_until_eol(bytes, read_idx, write_idx);
}
write_idx
}
/// Counts the minimum leading spaces of all non-empty lines except the first line.
///
/// Returns `None` if there are no non-empty lines except the first line.
const fn get_minimum_leading_spaces(bytes: &[u8]) -> Option<usize> {
// scan for leading spaces (ignoring first line and empty lines)
let mut i = 0;
// skip first line
i = advance_to_next_line(bytes, i);
let mut to_unindent = None;
// for remaining lines, count leading spaces
while i < bytes.len() {
let line_leading_spaces = count_spaces(bytes, i);
i += line_leading_spaces;
// line only had spaces, ignore for the count
if let Some(eol) = consume_eol(bytes, i) {
i = eol;
continue;
}
// this line has content, consider its leading spaces
if let Some(current) = to_unindent {
// .unwrap_or(usize::MAX) not available in const fn
if line_leading_spaces < current {
to_unindent = Some(line_leading_spaces);
}
} else {
to_unindent = Some(line_leading_spaces);
}
i = advance_to_next_line(bytes, i);
}
to_unindent
}
const fn advance_to_next_line(bytes: &[u8], mut i: usize) -> usize {
while i < bytes.len() {
if let Some(eol) = consume_eol(bytes, i) {
return eol;
}
i += 1;
}
i
}
/// Brings elements in `bytes` forward until `\n` (inclusive) or end of `source`.
///
/// `read_idx` must be greater than or equal to `write_idx`.
const fn copy_forward_until_eol(
bytes: &mut [u8],
mut read_idx: usize,
mut write_idx: usize,
) -> (usize, usize) {
assert!(read_idx >= write_idx);
while read_idx < bytes.len() {
let value = bytes[read_idx];
bytes[write_idx] = value;
read_idx += 1;
write_idx += 1;
if value == b'\n' {
break;
}
}
(read_idx, write_idx)
}
const fn count_spaces(bytes: &[u8], mut i: usize) -> usize {
let mut count = 0;
while i < bytes.len() && bytes[i] == b' ' {
count += 1;
i += 1;
}
count
}
const fn consume_eol(bytes: &[u8], i: usize) -> Option<usize> {
if bytes.len() == i {
// special case: treat end of buffer as EOL without consuming anything
Some(i)
} else if bytes.len() > i && bytes[i] == b'\n' {
Some(i + 1)
} else if bytes[i] == b'\r' && bytes.len() > i + 1 && bytes[i + 1] == b'\n' {
Some(i + 2)
} else {
None
}
}
pub const fn unindent_sized<const N: usize>(src: &[u8]) -> ([u8; N], usize) {
let mut out: [u8; N] = [0; N];
slice_copy_from_slice(&mut out, src);
let new_len = unindent_bytes(&mut out);
(out, new_len)
}
/// Helper for `py_run!` macro which unindents a string at compile time.
#[macro_export]
#[doc(hidden)]
macro_rules! unindent {
($value:expr) => {{
const RAW: &str = $value;
const LEN: usize = RAW.len();
const UNINDENTED: ([u8; LEN], usize) =
$crate::impl_::unindent::unindent_sized::<LEN>(RAW.as_bytes());
// SAFETY: this removes only spaces and preserves all other contents
unsafe { ::core::str::from_utf8_unchecked(UNINDENTED.0.split_at(UNINDENTED.1).0) }
}};
}
pub use crate::unindent;
/// Equivalent of the `unindent!` macro, but works at runtime.
pub fn unindent(s: &str) -> String {
let mut bytes = s.as_bytes().to_owned();
let unindented_size = unindent_bytes(&mut bytes);
bytes.resize(unindented_size, 0);
String::from_utf8(bytes).unwrap()
}
#[cfg(test)]
mod tests {
use super::*;
const SAMPLE_1_WITH_FIRST_LINE: &str = " first line
line one
line two
";
const UNINDENTED_1: &str = " first line\nline one\n\n line two\n";
const SAMPLE_2_EMPTY_FIRST_LINE: &str = "
line one
line two
";
const UNINDENTED_2: &str = "line one\n\n line two\n";
const SAMPLE_3_NO_INDENT: &str = "
no indent
here";
const UNINDENTED_3: &str = "no indent\n here";
const SAMPLE_4_NOOP: &str = "no indent\nhere\n but here";
const SAMPLE_5_EMPTY: &str = " \n \n";
const ALL_CASES: &[(&str, &str)] = &[
(SAMPLE_1_WITH_FIRST_LINE, UNINDENTED_1),
(SAMPLE_2_EMPTY_FIRST_LINE, UNINDENTED_2),
(SAMPLE_3_NO_INDENT, UNINDENTED_3),
(SAMPLE_4_NOOP, SAMPLE_4_NOOP),
(SAMPLE_5_EMPTY, SAMPLE_5_EMPTY),
];
// run const tests for each sample to ensure they work at compile time
#[test]
fn test_unindent_const() {
const UNINDENTED: &str = unindent!(SAMPLE_1_WITH_FIRST_LINE);
assert_eq!(UNINDENTED, UNINDENTED_1);
}
#[test]
fn test_unindent_const_removes_empty_first_line() {
const UNINDENTED: &str = unindent!(SAMPLE_2_EMPTY_FIRST_LINE);
assert_eq!(UNINDENTED, UNINDENTED_2);
}
#[test]
fn test_unindent_const_no_indent() {
const UNINDENTED: &str = unindent!(SAMPLE_3_NO_INDENT);
assert_eq!(UNINDENTED, UNINDENTED_3);
}
#[test]
fn test_unindent_macro_runtime() {
// this variation on the test ensures full coverage (const eval not included in coverage)
const INDENTED: &str = SAMPLE_1_WITH_FIRST_LINE;
const LEN: usize = INDENTED.len();
let (unindented, unindented_size) = unindent_sized::<LEN>(INDENTED.as_bytes());
let unindented = core::str::from_utf8(&unindented[..unindented_size]).unwrap();
assert_eq!(unindented, UNINDENTED_1);
}
#[test]
fn test_unindent_function() {
for (indented, expected) in ALL_CASES {
let unindented = unindent(indented);
assert_eq!(&unindented, expected);
}
}
}
+197
View File
@@ -0,0 +1,197 @@
#![warn(clippy::undocumented_unsafe_blocks)]
use core::{convert::Infallible, marker::PhantomData, ops::Deref};
use crate::{
ffi, types::PyNone, Bound, IntoPyObject, IntoPyObjectExt, Py, PyAny, PyResult, Python,
};
/// Used to wrap values in `Option<T>` for default arguments.
pub trait SomeWrap<T> {
fn wrap(self) -> Option<T>;
}
impl<T> SomeWrap<T> for T {
fn wrap(self) -> Option<T> {
Some(self)
}
}
impl<T> SomeWrap<T> for Option<T> {
fn wrap(self) -> Self {
self
}
}
pub struct OkWrapper<T>(OkWrapperInner<T>);
pub struct OkWrapperInner<T>(PhantomData<T>);
impl<T> OkWrapper<T> {
pub fn new(_: &T) -> Self {
Self(OkWrapperInner(PhantomData))
}
}
impl<T> Deref for OkWrapper<T> {
type Target = OkWrapperInner<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T, E> OkWrapper<Result<T, E>> {
pub fn ok_wrap(&self, value: Result<T, E>) -> Result<T, E> {
value
}
}
impl<T> OkWrapperInner<T> {
pub fn ok_wrap(&self, value: T) -> Result<T, Infallible> {
Ok(value)
}
}
// Hierarchy of conversions used in the function return type machinery
pub struct Converter<T>(EmptyTupleConverter<T>);
pub struct EmptyTupleConverter<T>(IntoPyObjectConverter<T>);
pub struct IntoPyObjectConverter<T>(UnknownReturnResultType<T>);
pub struct UnknownReturnResultType<T>(UnknownReturnType<T>);
pub struct UnknownReturnType<T>(PhantomData<T>);
pub fn converter<T>(_: &T) -> Converter<T> {
Converter(EmptyTupleConverter(IntoPyObjectConverter(
UnknownReturnResultType(UnknownReturnType(PhantomData)),
)))
}
impl<T> Deref for Converter<T> {
type Target = EmptyTupleConverter<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> Deref for EmptyTupleConverter<T> {
type Target = IntoPyObjectConverter<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> Deref for IntoPyObjectConverter<T> {
type Target = UnknownReturnResultType<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> Deref for UnknownReturnResultType<T> {
type Target = UnknownReturnType<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl EmptyTupleConverter<PyResult<()>> {
#[inline]
pub fn map_into_ptr(&self, py: Python<'_>, obj: PyResult<()>) -> PyResult<*mut ffi::PyObject> {
obj.map(|_| PyNone::get(py).to_owned().into_ptr())
}
#[inline]
pub fn map_into_pyobject(&self, py: Python<'_>, obj: PyResult<()>) -> PyResult<Py<PyAny>> {
obj.map(|_| PyNone::get(py).to_owned().into_any().unbind())
}
}
impl<'py, T: IntoPyObject<'py>> IntoPyObjectConverter<T> {
#[inline]
pub fn wrap(&self, obj: T) -> Result<T, Infallible> {
Ok(obj)
}
}
impl<'py, T: IntoPyObject<'py>, E> IntoPyObjectConverter<Result<T, E>> {
#[inline]
pub fn wrap(&self, obj: Result<T, E>) -> Result<T, E> {
obj
}
#[inline]
pub fn map_into_pyobject(&self, py: Python<'py>, obj: PyResult<T>) -> PyResult<Py<PyAny>>
where
T: IntoPyObject<'py>,
{
obj.and_then(|obj| obj.into_py_any(py))
}
#[inline]
pub fn map_into_ptr(&self, py: Python<'py>, obj: PyResult<T>) -> PyResult<*mut ffi::PyObject>
where
T: IntoPyObject<'py>,
{
obj.and_then(|obj| obj.into_bound_py_any(py))
.map(Bound::into_ptr)
}
}
impl<T, E> UnknownReturnResultType<Result<T, E>> {
#[inline]
pub fn wrap<'py>(&self, _: Result<T, E>) -> Result<T, E>
where
T: IntoPyObject<'py>,
{
unreachable!("should be handled by IntoPyObjectConverter")
}
}
impl<T> UnknownReturnType<T> {
#[inline]
pub fn wrap<'py>(&self, _: T) -> T
where
T: IntoPyObject<'py>,
{
unreachable!("should be handled by IntoPyObjectConverter")
}
#[inline]
pub fn map_into_pyobject<'py>(&self, _: Python<'py>, _: PyResult<T>) -> PyResult<Py<PyAny>>
where
T: IntoPyObject<'py>,
{
unreachable!("should be handled by IntoPyObjectConverter")
}
#[inline]
pub fn map_into_ptr<'py>(&self, _: Python<'py>, _: PyResult<T>) -> PyResult<*mut ffi::PyObject>
where
T: IntoPyObject<'py>,
{
unreachable!("should be handled by IntoPyObjectConverter")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wrap_option() {
let a: Option<u8> = SomeWrap::wrap(42);
assert_eq!(a, Some(42));
let b: Option<u8> = SomeWrap::wrap(None);
assert_eq!(b, None);
}
#[test]
fn wrap_result() {
let a = 42;
let Ok(a) = OkWrapper::new(&a).ok_wrap(a);
assert_eq!(a, 42);
let b = Result::<_, String>::Ok(42);
let b = OkWrapper::new(&b).ok_wrap(b);
assert_eq!(b, Ok(42));
}
}
+578
View File
@@ -0,0 +1,578 @@
//! Runtime inspection of objects exposed to Python.
//!
//! Tracking issue: <https://github.com/PyO3/pyo3/issues/2454>.
use crate::impl_::introspection::{escape_json_string, escaped_json_string_len};
use core::fmt::{self, Display, Write};
/// Builds a type hint from a module name and a member name in the module
///
/// ```
/// use pyo3::type_hint_identifier;
/// use pyo3::inspect::PyStaticExpr;
///
/// const T: PyStaticExpr = type_hint_identifier!("datetime", "date");
/// assert_eq!(T.to_string(), "datetime.date");
///
/// const T2: PyStaticExpr = type_hint_identifier!("builtins", "int");
/// assert_eq!(T2.to_string(), "int");
/// ```
#[macro_export]
macro_rules! type_hint_identifier {
("builtins", $name:expr) => {
$crate::inspect::PyStaticExpr::Name { id: $name }
};
($module:expr, $name:expr) => {
$crate::inspect::PyStaticExpr::Attribute {
value: &$crate::inspect::PyStaticExpr::Name { id: $module },
attr: $name,
}
};
}
pub(crate) use type_hint_identifier;
/// Builds the union of multiple type hints
///
/// ```
/// use pyo3::{type_hint_identifier, type_hint_union};
/// use pyo3::inspect::PyStaticExpr;
///
/// const T: PyStaticExpr = type_hint_union!(type_hint_identifier!("builtins", "int"), type_hint_identifier!("builtins", "float"));
/// assert_eq!(T.to_string(), "int | float");
/// ```
#[macro_export]
macro_rules! type_hint_union {
($e:expr) => { $e };
($l:expr , $($r:expr),+) => { $crate::inspect::PyStaticExpr::BinOp {
left: &$l,
op: $crate::inspect::PyStaticOperator::BitOr,
right: &type_hint_union!($($r),+),
} };
}
pub(crate) use type_hint_union;
/// Builds a subscribed type hint
///
/// ```
/// use pyo3::{type_hint_identifier, type_hint_subscript};
/// use pyo3::inspect::PyStaticExpr;
///
/// const T: PyStaticExpr = type_hint_subscript!(type_hint_identifier!("collections.abc", "Sequence"), type_hint_identifier!("builtins", "float"));
/// assert_eq!(T.to_string(), "collections.abc.Sequence[float]");
///
/// const T2: PyStaticExpr = type_hint_subscript!(type_hint_identifier!("builtins", "dict"), type_hint_identifier!("builtins", "str"), type_hint_identifier!("builtins", "float"));
/// assert_eq!(T2.to_string(), "dict[str, float]");
/// ```
#[macro_export]
macro_rules! type_hint_subscript {
($l:expr, $r:expr) => {
$crate::inspect::PyStaticExpr::Subscript {
value: &$l,
slice: &$r
}
};
($l:expr, $($r:expr),*) => {
$crate::inspect::PyStaticExpr::Subscript {
value: &$l,
slice: &$crate::inspect::PyStaticExpr::Tuple { elts: &[$($r),*] }
}
};
}
pub(crate) use type_hint_subscript;
/// A Python expression.
///
/// This is the `expr` production of the [Python `ast` module grammar](https://docs.python.org/3/library/ast.html#abstract-grammar)
///
/// This struct aims at being used in `const` contexts like in [`FromPyObject::INPUT_TYPE`](crate::FromPyObject::INPUT_TYPE) and [`IntoPyObject::OUTPUT_TYPE`](crate::IntoPyObject::OUTPUT_TYPE).
///
/// Use macros like [`type_hint_identifier`], [`type_hint_union`] and [`type_hint_subscript`] to construct values.
#[derive(Clone, Copy)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum PyStaticExpr {
/// A constant like `None` or `123`
Constant { value: PyStaticConstant },
/// A name
Name { id: &'static str },
/// An attribute `value.attr`
Attribute {
value: &'static Self,
attr: &'static str,
},
/// A binary operator
BinOp {
left: &'static Self,
op: PyStaticOperator,
right: &'static Self,
},
/// A tuple
Tuple { elts: &'static [Self] },
/// A list
List { elts: &'static [Self] },
/// A subscript `value[slice]`
Subscript {
value: &'static Self,
slice: &'static Self,
},
/// A `#[pyclass]` type. This is separated type for introspection reasons.
PyClass(PyClassNameStaticExpr),
}
/// Serialize the type for introspection and return the number of written bytes
#[doc(hidden)]
pub const fn serialize_for_introspection(expr: &PyStaticExpr, mut output: &mut [u8]) -> usize {
let original_len = output.len();
match expr {
PyStaticExpr::Constant { value } => match value {
PyStaticConstant::None => {
output = write_slice_and_move_forward(
b"{\"type\":\"constant\",\"kind\":\"none\"}",
output,
)
}
PyStaticConstant::Bool(value) => {
output = write_slice_and_move_forward(
if *value {
b"{\"type\":\"constant\",\"kind\":\"bool\",\"value\":true}"
} else {
b"{\"type\":\"constant\",\"kind\":\"bool\",\"value\":false}"
},
output,
)
}
PyStaticConstant::Int(value) => {
output = write_slice_and_move_forward(
b"{\"type\":\"constant\",\"kind\":\"int\",\"value\":\"",
output,
);
output = write_slice_and_move_forward(value.as_bytes(), output);
output = write_slice_and_move_forward(b"\"}", output);
}
PyStaticConstant::Float(value) => {
output = write_slice_and_move_forward(
b"{\"type\":\"constant\",\"kind\":\"float\",\"value\":\"",
output,
);
output = write_slice_and_move_forward(value.as_bytes(), output);
output = write_slice_and_move_forward(b"\"}", output);
}
PyStaticConstant::Str(value) => {
output = write_slice_and_move_forward(
b"{\"type\":\"constant\",\"kind\":\"str\",\"value\":",
output,
);
output = write_json_string_and_move_forward(value, output);
output = write_slice_and_move_forward(b"}", output);
}
PyStaticConstant::Ellipsis => {
output = write_slice_and_move_forward(
b"{\"type\":\"constant\",\"kind\":\"ellipsis\"}",
output,
)
}
},
PyStaticExpr::Name { id } => {
output = write_slice_and_move_forward(b"{\"type\":\"name\",\"id\":\"", output);
output = write_slice_and_move_forward(id.as_bytes(), output);
output = write_slice_and_move_forward(b"\"}", output);
}
PyStaticExpr::Attribute { value, attr } => {
output = write_slice_and_move_forward(b"{\"type\":\"attribute\",\"value\":", output);
output = write_expr_and_move_forward(value, output);
output = write_slice_and_move_forward(b",\"attr\":\"", output);
output = write_slice_and_move_forward(attr.as_bytes(), output);
output = write_slice_and_move_forward(b"\"}", output);
}
PyStaticExpr::BinOp { left, op, right } => {
output = write_slice_and_move_forward(b"{\"type\":\"binop\",\"left\":", output);
output = write_expr_and_move_forward(left, output);
output = write_slice_and_move_forward(b",\"op\":\"", output);
output = write_slice_and_move_forward(
match op {
PyStaticOperator::BitOr => b"bitor",
},
output,
);
output = write_slice_and_move_forward(b"\",\"right\":", output);
output = write_expr_and_move_forward(right, output);
output = write_slice_and_move_forward(b"}", output);
}
PyStaticExpr::Tuple { elts } => {
output = write_container_and_move_forward(b"tuple", elts, output);
}
PyStaticExpr::List { elts } => {
output = write_container_and_move_forward(b"list", elts, output);
}
PyStaticExpr::Subscript { value, slice } => {
output = write_slice_and_move_forward(b"{\"type\":\"subscript\",\"value\":", output);
output = write_expr_and_move_forward(value, output);
output = write_slice_and_move_forward(b",\"slice\":", output);
output = write_expr_and_move_forward(slice, output);
output = write_slice_and_move_forward(b"}", output);
}
PyStaticExpr::PyClass(expr) => {
output = write_slice_and_move_forward(b"{\"type\":\"id\",\"id\":\"", output);
output = write_slice_and_move_forward(expr.introspection_id.as_bytes(), output);
output = write_slice_and_move_forward(b"\"}", output);
}
}
original_len - output.len()
}
/// Length required by [`serialize_for_introspection`]
#[doc(hidden)]
pub const fn serialized_len_for_introspection(expr: &PyStaticExpr) -> usize {
match expr {
PyStaticExpr::Constant { value } => match value {
PyStaticConstant::None => 33,
PyStaticConstant::Bool(value) => 42 + if *value { 4 } else { 5 },
PyStaticConstant::Int(value) => 43 + value.len(),
PyStaticConstant::Float(value) => 45 + value.len(),
PyStaticConstant::Str(value) => 43 + escaped_json_string_len(value),
PyStaticConstant::Ellipsis => 37,
},
PyStaticExpr::Name { id } => 23 + id.len(),
PyStaticExpr::Attribute { value, attr } => {
39 + serialized_len_for_introspection(value) + attr.len()
}
PyStaticExpr::BinOp { left, op, right } => {
41 + serialized_len_for_introspection(left)
+ match op {
PyStaticOperator::BitOr => 5,
}
+ serialized_len_for_introspection(right)
}
PyStaticExpr::Tuple { elts } => 5 + serialized_container_len_for_introspection(elts),
PyStaticExpr::List { elts } => 4 + serialized_container_len_for_introspection(elts),
PyStaticExpr::Subscript { value, slice } => {
38 + serialized_len_for_introspection(value) + serialized_len_for_introspection(slice)
}
PyStaticExpr::PyClass(expr) => 21 + expr.introspection_id.len(),
}
}
impl fmt::Display for PyStaticExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Constant { value } => match value {
PyStaticConstant::None => f.write_str("None"),
PyStaticConstant::Bool(value) => f.write_str(if *value { "True" } else { "False" }),
PyStaticConstant::Int(value) => f.write_str(value),
PyStaticConstant::Float(value) => {
f.write_str(value)?;
if !value.contains(['.', 'e', 'E']) {
// Makes sure it's not parsed as an int
f.write_char('.')?;
}
Ok(())
}
PyStaticConstant::Str(value) => write!(f, "{value:?}"),
PyStaticConstant::Ellipsis => f.write_str("..."),
},
Self::Name { id, .. } => f.write_str(id),
Self::Attribute { value, attr } => {
value.fmt(f)?;
f.write_str(".")?;
f.write_str(attr)
}
Self::BinOp { left, op, right } => {
left.fmt(f)?;
f.write_char(' ')?;
f.write_char(match op {
PyStaticOperator::BitOr => '|',
})?;
f.write_char(' ')?;
right.fmt(f)
}
Self::Tuple { elts } => {
f.write_char('(')?;
fmt_elements(elts, f)?;
if elts.len() == 1 {
f.write_char(',')?;
}
f.write_char(')')
}
Self::List { elts } => {
f.write_char('[')?;
fmt_elements(elts, f)?;
f.write_char(']')
}
Self::Subscript { value, slice } => {
value.fmt(f)?;
f.write_char('[')?;
if let PyStaticExpr::Tuple { elts } = slice {
// We don't display the tuple parentheses
fmt_elements(elts, f)?;
} else {
slice.fmt(f)?;
}
f.write_char(']')
}
Self::PyClass(expr) => expr.expr.fmt(f),
}
}
}
/// A PyO3 extension to the Python AST to know more about [`PyStaticExpr::Constant`].
///
/// This enables advanced features like escaping.
#[derive(Clone, Copy)]
#[non_exhaustive]
pub enum PyStaticConstant {
/// None
None,
/// The `True` and `False` booleans
Bool(bool),
/// `int` value written in base 10 (`[+-]?[0-9]+`)
Int(&'static str),
/// `float` value written in base-10 (`[+-]?[0-9]*(.[0-9]*)*([eE])[0-9]*`), not including Inf and NaN
Float(&'static str),
/// `str` value unescaped and without quotes
Str(&'static str),
/// `...` value
Ellipsis,
}
/// An operator used in [`PyStaticExpr::BinOp`].
#[derive(Clone, Copy)]
#[non_exhaustive]
pub enum PyStaticOperator {
/// `|` operator
BitOr,
}
const fn write_slice_and_move_forward<'a>(value: &[u8], output: &'a mut [u8]) -> &'a mut [u8] {
// TODO: use copy_from_slice with MSRV 1.87+
let mut i = 0;
while i < value.len() {
output[i] = value[i];
i += 1;
}
output.split_at_mut(value.len()).1
}
const fn write_json_string_and_move_forward<'a>(value: &str, output: &'a mut [u8]) -> &'a mut [u8] {
output[0] = b'"';
let output = output.split_at_mut(1).1;
let written = escape_json_string(value, output);
output[written] = b'"';
output.split_at_mut(written + 1).1
}
const fn write_expr_and_move_forward<'a>(
value: &PyStaticExpr,
output: &'a mut [u8],
) -> &'a mut [u8] {
let written = serialize_for_introspection(value, output);
output.split_at_mut(written).1
}
const fn write_container_and_move_forward<'a>(
name: &'static [u8],
elts: &[PyStaticExpr],
mut output: &'a mut [u8],
) -> &'a mut [u8] {
output = write_slice_and_move_forward(b"{\"type\":\"", output);
output = write_slice_and_move_forward(name, output);
output = write_slice_and_move_forward(b"\",\"elts\":[", output);
let mut i = 0;
while i < elts.len() {
if i > 0 {
output = write_slice_and_move_forward(b",", output);
}
output = write_expr_and_move_forward(&elts[i], output);
i += 1;
}
write_slice_and_move_forward(b"]}", output)
}
const fn serialized_container_len_for_introspection(elts: &[PyStaticExpr]) -> usize {
let mut len = 21;
let mut i = 0;
while i < elts.len() {
if i > 0 {
len += 1;
}
len += serialized_len_for_introspection(&elts[i]);
i += 1;
}
len
}
fn fmt_elements(elts: &[PyStaticExpr], f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, elt) in elts.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
elt.fmt(f)?;
}
Ok(())
}
/// The full name of a `#[pyclass]` inside a [`PyStaticExpr`].
///
/// To get the underlying [`PyStaticExpr`] use [`expr`](PyClassNameStaticExpr::expr).
#[derive(Clone, Copy)]
pub struct PyClassNameStaticExpr {
expr: &'static PyStaticExpr,
introspection_id: &'static str,
}
impl PyClassNameStaticExpr {
#[doc(hidden)]
#[inline]
pub const fn new(expr: &'static PyStaticExpr, introspection_id: &'static str) -> Self {
Self {
expr,
introspection_id,
}
}
/// The pyclass type as an expression like `module.name`
///
/// This is based on the `name` and `module` parameter of the `#[pyclass]` macro.
/// The `module` part might not be a valid module from which the type can be imported.
#[inline]
pub const fn expr(&self) -> &'static PyStaticExpr {
self.expr
}
}
impl AsRef<PyStaticExpr> for PyClassNameStaticExpr {
#[inline]
fn as_ref(&self) -> &PyStaticExpr {
self.expr
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_string() {
const T: PyStaticExpr = type_hint_subscript!(
type_hint_identifier!("builtins", "dict"),
type_hint_union!(
type_hint_identifier!("builtins", "int"),
type_hint_subscript!(
type_hint_identifier!("typing", "Literal"),
PyStaticExpr::Constant {
value: PyStaticConstant::Str("\0\t\\\"")
}
)
),
type_hint_identifier!("datetime", "time")
);
assert_eq!(
T.to_string(),
"dict[int | typing.Literal[\"\\0\\t\\\\\\\"\"], datetime.time]"
)
}
#[test]
fn test_serialize_for_introspection() {
fn check_serialization(expr: PyStaticExpr, expected: &str) {
let mut out = vec![0; serialized_len_for_introspection(&expr)];
serialize_for_introspection(&expr, &mut out);
assert_eq!(core::str::from_utf8(&out).unwrap(), expected)
}
check_serialization(
PyStaticExpr::Constant {
value: PyStaticConstant::None,
},
r#"{"type":"constant","kind":"none"}"#,
);
check_serialization(
type_hint_identifier!("builtins", "int"),
r#"{"type":"name","id":"int"}"#,
);
check_serialization(
type_hint_identifier!("datetime", "date"),
r#"{"type":"attribute","value":{"type":"name","id":"datetime"},"attr":"date"}"#,
);
check_serialization(
type_hint_union!(
type_hint_identifier!("builtins", "int"),
type_hint_identifier!("builtins", "float")
),
r#"{"type":"binop","left":{"type":"name","id":"int"},"op":"bitor","right":{"type":"name","id":"float"}}"#,
);
check_serialization(
PyStaticExpr::Tuple {
elts: &[type_hint_identifier!("builtins", "list")],
},
r#"{"type":"tuple","elts":[{"type":"name","id":"list"}]}"#,
);
check_serialization(
PyStaticExpr::List {
elts: &[type_hint_identifier!("builtins", "list")],
},
r#"{"type":"list","elts":[{"type":"name","id":"list"}]}"#,
);
check_serialization(
type_hint_subscript!(
type_hint_identifier!("builtins", "list"),
type_hint_identifier!("builtins", "int")
),
r#"{"type":"subscript","value":{"type":"name","id":"list"},"slice":{"type":"name","id":"int"}}"#,
);
check_serialization(
PyStaticExpr::PyClass(PyClassNameStaticExpr::new(
&type_hint_identifier!("builtins", "foo"),
"foo",
)),
r#"{"type":"id","id":"foo"}"#,
);
check_serialization(
PyStaticExpr::Constant {
value: PyStaticConstant::Bool(true),
},
r#"{"type":"constant","kind":"bool","value":true}"#,
);
check_serialization(
PyStaticExpr::Constant {
value: PyStaticConstant::Bool(false),
},
r#"{"type":"constant","kind":"bool","value":false}"#,
);
check_serialization(
PyStaticExpr::Constant {
value: PyStaticConstant::Int("-123"),
},
r#"{"type":"constant","kind":"int","value":"-123"}"#,
);
check_serialization(
PyStaticExpr::Constant {
value: PyStaticConstant::Float("-2.1"),
},
r#"{"type":"constant","kind":"float","value":"-2.1"}"#,
);
check_serialization(
PyStaticExpr::Constant {
value: PyStaticConstant::Float("+2.1e10"),
},
r#"{"type":"constant","kind":"float","value":"+2.1e10"}"#,
);
check_serialization(
PyStaticExpr::Constant {
value: PyStaticConstant::Str("abc(1)"),
},
r#"{"type":"constant","kind":"str","value":"abc(1)"}"#,
);
check_serialization(
PyStaticExpr::Constant {
value: PyStaticConstant::Str("\"\\/\x08\x0C\n\r\t\0\x19a"),
},
r#"{"type":"constant","kind":"str","value":"\"\\/\b\f\n\r\t\u0000\u0019a"}"#,
);
check_serialization(
PyStaticExpr::Constant {
value: PyStaticConstant::Ellipsis,
},
r#"{"type":"constant","kind":"ellipsis"}"#,
);
}
}
+2954
View File
File diff suppressed because it is too large. Load diff
+8
View File
@@ -0,0 +1,8 @@
//! Holding place for code which is not intended to be reachable from outside of PyO3.
#[macro_use]
pub(crate) mod macros;
pub(crate) mod get_slot;
pub(crate) mod pyclass_init;
pub(crate) mod state;
+284
View File
@@ -0,0 +1,284 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
use crate::{
ffi,
types::{PyType, PyTypeMethods},
Borrowed, Bound,
};
use core::ffi::c_int;
impl Bound<'_, PyType> {
#[inline]
pub(crate) fn get_slot<const S: c_int>(&self, slot: Slot<S>) -> <Slot<S> as GetSlotImpl>::Type
where
Slot<S>: GetSlotImpl,
{
// SAFETY: `self` is a valid type object.
unsafe {
slot.get_slot(
self.as_type_ptr(),
#[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
is_runtime_3_10(self.py()),
)
}
}
}
impl Borrowed<'_, '_, PyType> {
#[inline]
pub(crate) fn get_slot<const S: c_int>(self, slot: Slot<S>) -> <Slot<S> as GetSlotImpl>::Type
where
Slot<S>: GetSlotImpl,
{
// SAFETY: `self` is a valid type object.
unsafe {
slot.get_slot(
self.as_type_ptr(),
#[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
is_runtime_3_10(self.py()),
)
}
}
}
/// Gets a slot from a raw FFI pointer.
///
/// Safety:
/// - `ty` must be a valid non-null pointer to a `PyTypeObject`.
/// - The Python runtime must be initialized
pub(crate) unsafe fn get_slot<const S: c_int>(
ty: *mut ffi::PyTypeObject,
slot: Slot<S>,
) -> <Slot<S> as GetSlotImpl>::Type
where
Slot<S>: GetSlotImpl,
{
unsafe {
slot.get_slot(
ty,
// SAFETY: the Python runtime is initialized
#[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
is_runtime_3_10(crate::Python::assume_attached()),
)
}
}
pub(crate) trait GetSlotImpl {
type Type;
/// Gets the requested slot from a type object.
///
/// Safety:
/// - `ty` must be a valid non-null pointer to a `PyTypeObject`.
/// - `is_runtime_3_10` must be `false` if the runtime is not Python 3.10 or later.
unsafe fn get_slot(
self,
ty: *mut ffi::PyTypeObject,
#[cfg(all(Py_LIMITED_API, not(Py_3_10)))] is_runtime_3_10: bool,
) -> Self::Type;
}
#[derive(Copy, Clone)]
pub(crate) struct Slot<const S: c_int>;
macro_rules! impl_slots {
($($name:ident: ($slot:ident, $field:ident) -> $tp:ty),+ $(,)?) => {
$(
pub (crate) const $name: Slot<{ ffi::$slot }> = Slot;
impl GetSlotImpl for Slot<{ ffi::$slot }> {
type Type = $tp;
#[inline]
unsafe fn get_slot(
self,
ty: *mut ffi::PyTypeObject,
#[cfg(all(Py_LIMITED_API, not(Py_3_10)))] is_runtime_3_10: bool
) -> Self::Type {
#[cfg(not(Py_LIMITED_API))]
{
unsafe {(*ty).$field }
}
#[cfg(Py_LIMITED_API)]
{
#[cfg(not(Py_3_10))]
{
// Calling PyType_GetSlot on static types is not valid before Python 3.10
// ... so the workaround is to first do a runtime check for these versions
// (3.8, 3.9) and then look in the type object anyway. This is only ok
// because we know that the interpreter is not going to change the size
// of the type objects for these historical versions.
if !is_runtime_3_10 && unsafe {ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE)} == 0
{
return unsafe {(*ty.cast::<PyTypeObject39Snapshot>()).$field};
}
}
// SAFETY: slot type is set carefully to be valid
unsafe {core::mem::transmute(ffi::PyType_GetSlot(ty, ffi::$slot))}
}
}
}
)*
};
}
// Slots are implemented on-demand as needed.)
impl_slots! {
TP_NEW: (Py_tp_new, tp_new) -> Option<ffi::newfunc>,
TP_DEALLOC: (Py_tp_dealloc, tp_dealloc) -> Option<ffi::destructor>,
TP_BASE: (Py_tp_base, tp_base) -> *mut ffi::PyTypeObject,
TP_CLEAR: (Py_tp_clear, tp_clear) -> Option<ffi::inquiry>,
TP_DESCR_GET: (Py_tp_descr_get, tp_descr_get) -> Option<ffi::descrgetfunc>,
TP_FREE: (Py_tp_free, tp_free) -> Option<ffi::freefunc>,
TP_TRAVERSE: (Py_tp_traverse, tp_traverse) -> Option<ffi::traverseproc>,
}
#[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
fn is_runtime_3_10(py: crate::Python<'_>) -> bool {
use crate::sync::PyOnceLock;
static IS_RUNTIME_3_10: PyOnceLock<bool> = PyOnceLock::new();
*IS_RUNTIME_3_10.get_or_init(py, || py.version_info() >= (3, 10))
}
#[repr(C)]
#[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
pub struct PyNumberMethods39Snapshot {
pub nb_add: Option<ffi::binaryfunc>,
pub nb_subtract: Option<ffi::binaryfunc>,
pub nb_multiply: Option<ffi::binaryfunc>,
pub nb_remainder: Option<ffi::binaryfunc>,
pub nb_divmod: Option<ffi::binaryfunc>,
pub nb_power: Option<ffi::ternaryfunc>,
pub nb_negative: Option<ffi::unaryfunc>,
pub nb_positive: Option<ffi::unaryfunc>,
pub nb_absolute: Option<ffi::unaryfunc>,
pub nb_bool: Option<ffi::inquiry>,
pub nb_invert: Option<ffi::unaryfunc>,
pub nb_lshift: Option<ffi::binaryfunc>,
pub nb_rshift: Option<ffi::binaryfunc>,
pub nb_and: Option<ffi::binaryfunc>,
pub nb_xor: Option<ffi::binaryfunc>,
pub nb_or: Option<ffi::binaryfunc>,
pub nb_int: Option<ffi::unaryfunc>,
pub nb_reserved: *mut core::ffi::c_void,
pub nb_float: Option<ffi::unaryfunc>,
pub nb_inplace_add: Option<ffi::binaryfunc>,
pub nb_inplace_subtract: Option<ffi::binaryfunc>,
pub nb_inplace_multiply: Option<ffi::binaryfunc>,
pub nb_inplace_remainder: Option<ffi::binaryfunc>,
pub nb_inplace_power: Option<ffi::ternaryfunc>,
pub nb_inplace_lshift: Option<ffi::binaryfunc>,
pub nb_inplace_rshift: Option<ffi::binaryfunc>,
pub nb_inplace_and: Option<ffi::binaryfunc>,
pub nb_inplace_xor: Option<ffi::binaryfunc>,
pub nb_inplace_or: Option<ffi::binaryfunc>,
pub nb_floor_divide: Option<ffi::binaryfunc>,
pub nb_true_divide: Option<ffi::binaryfunc>,
pub nb_inplace_floor_divide: Option<ffi::binaryfunc>,
pub nb_inplace_true_divide: Option<ffi::binaryfunc>,
pub nb_index: Option<ffi::unaryfunc>,
pub nb_matrix_multiply: Option<ffi::binaryfunc>,
pub nb_inplace_matrix_multiply: Option<ffi::binaryfunc>,
}
#[repr(C)]
#[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
pub struct PySequenceMethods39Snapshot {
pub sq_length: Option<ffi::lenfunc>,
pub sq_concat: Option<ffi::binaryfunc>,
pub sq_repeat: Option<ffi::ssizeargfunc>,
pub sq_item: Option<ffi::ssizeargfunc>,
pub was_sq_slice: *mut core::ffi::c_void,
pub sq_ass_item: Option<ffi::ssizeobjargproc>,
pub was_sq_ass_slice: *mut core::ffi::c_void,
pub sq_contains: Option<ffi::objobjproc>,
pub sq_inplace_concat: Option<ffi::binaryfunc>,
pub sq_inplace_repeat: Option<ffi::ssizeargfunc>,
}
#[repr(C)]
#[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
pub struct PyMappingMethods39Snapshot {
pub mp_length: Option<ffi::lenfunc>,
pub mp_subscript: Option<ffi::binaryfunc>,
pub mp_ass_subscript: Option<ffi::objobjargproc>,
}
#[repr(C)]
#[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
pub struct PyAsyncMethods39Snapshot {
pub am_await: Option<ffi::unaryfunc>,
pub am_aiter: Option<ffi::unaryfunc>,
pub am_anext: Option<ffi::unaryfunc>,
}
#[repr(C)]
#[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
pub struct PyBufferProcs39Snapshot {
// not available in limited api, but structure needs to have the right size
pub bf_getbuffer: *mut core::ffi::c_void,
pub bf_releasebuffer: *mut core::ffi::c_void,
}
/// Snapshot of the structure of PyTypeObject for Python 3.8 through 3.9.
///
/// This is used as a fallback for static types in abi3 when the Python version is less than 3.10;
/// this is a bit of a hack but there's no better option and the structure of the type object is
/// not going to change for those historical versions.
#[repr(C)]
#[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
struct PyTypeObject39Snapshot {
pub ob_base: ffi::PyVarObject,
pub tp_name: *const core::ffi::c_char,
pub tp_basicsize: ffi::Py_ssize_t,
pub tp_itemsize: ffi::Py_ssize_t,
pub tp_dealloc: Option<ffi::destructor>,
pub tp_vectorcall_offset: ffi::Py_ssize_t,
pub tp_getattr: Option<ffi::getattrfunc>,
pub tp_setattr: Option<ffi::setattrfunc>,
pub tp_as_async: *mut PyAsyncMethods39Snapshot,
pub tp_repr: Option<ffi::reprfunc>,
pub tp_as_number: *mut PyNumberMethods39Snapshot,
pub tp_as_sequence: *mut PySequenceMethods39Snapshot,
pub tp_as_mapping: *mut PyMappingMethods39Snapshot,
pub tp_hash: Option<ffi::hashfunc>,
pub tp_call: Option<ffi::ternaryfunc>,
pub tp_str: Option<ffi::reprfunc>,
pub tp_getattro: Option<ffi::getattrofunc>,
pub tp_setattro: Option<ffi::setattrofunc>,
pub tp_as_buffer: *mut PyBufferProcs39Snapshot,
pub tp_flags: core::ffi::c_ulong,
pub tp_doc: *const core::ffi::c_char,
pub tp_traverse: Option<ffi::traverseproc>,
pub tp_clear: Option<ffi::inquiry>,
pub tp_richcompare: Option<ffi::richcmpfunc>,
pub tp_weaklistoffset: ffi::Py_ssize_t,
pub tp_iter: Option<ffi::getiterfunc>,
pub tp_iternext: Option<ffi::iternextfunc>,
pub tp_methods: *mut ffi::PyMethodDef,
pub tp_members: *mut ffi::PyMemberDef,
pub tp_getset: *mut ffi::PyGetSetDef,
pub tp_base: *mut ffi::PyTypeObject,
pub tp_dict: *mut ffi::PyObject,
pub tp_descr_get: Option<ffi::descrgetfunc>,
pub tp_descr_set: Option<ffi::descrsetfunc>,
pub tp_dictoffset: ffi::Py_ssize_t,
pub tp_init: Option<ffi::initproc>,
pub tp_alloc: Option<ffi::allocfunc>,
pub tp_new: Option<ffi::newfunc>,
pub tp_free: Option<ffi::freefunc>,
pub tp_is_gc: Option<ffi::inquiry>,
pub tp_bases: *mut ffi::PyObject,
pub tp_mro: *mut ffi::PyObject,
pub tp_cache: *mut ffi::PyObject,
pub tp_subclasses: *mut ffi::PyObject,
pub tp_weaklist: *mut ffi::PyObject,
pub tp_del: Option<ffi::destructor>,
pub tp_version_tag: core::ffi::c_uint,
pub tp_finalize: Option<ffi::destructor>,
pub tp_vectorcall: Option<ffi::vectorcallfunc>,
}
+90
View File
@@ -0,0 +1,90 @@
/// Polyfill of `cfg_select` for MSRV < 1.95
///
/// Note that this polyfill does not work in type position, so it is not as powerful as the
/// real `cfg_select` macro. That is a limitation that PyO3 can live with for now.
#[doc(hidden)]
#[cfg(not(cfg_select))]
macro_rules! cfg_select {
// Final arm with _ to match any non-existing clauses
(
@parsed($($clauses:meta),*)
_ => $final_arm:expr $(,)?
) => {
#[cfg(not(any($($clauses),+)))]
{
$final_arm
}
};
// Final arm has a clause, handle it plus a compile error if no clauses match
// Need to allow for trailing comma on final expression to be optional
(
@parsed($($clauses:meta),*)
$cfg:meta => $final_arm:expr $(,)?
) => {
#[cfg(all($cfg, not(any($($clauses),*))))]
{
$final_arm
}
#[cfg(not(any($($clauses,)* $cfg)))]
compile_error!(
"cfg_select! requires a final `_ => expr` arm to be used as a fallback when no other cfg matches"
);
};
// Non-terminating expression arm requires trailing comma
(
@parsed($($clauses:meta),*)
$cfg:meta => $arm:expr, $($rest:tt)*
) => {
#[cfg(all($cfg, not(any($($clauses),*))))]
{
$arm
}
cfg_select! {
@parsed($($clauses,)* $cfg)
$($rest)*
}
};
// Non-terminating block doesn't require trailing comma
(
@parsed($($clauses:meta),*)
$cfg:meta => $arm:block $($rest:tt)*
) => {
#[cfg(all($cfg, not(any($($clauses),*))))]
$arm
cfg_select! {
@parsed($($clauses,)* $cfg)
$($rest)*
}
};
(
$cfg:meta => $($rest:tt)*
) => {
{
cfg_select! {
@parsed()
$cfg => $($rest)*
}
}
};
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(not(cfg_select))]
fn test_cfg_select_polyfill_short_circuit() {
cfg_select! {
all() => {},
all() => {
unreachable!("the first arm should be selected, so this arm should not be evaluated");
}
}
}
}
+200
View File
@@ -0,0 +1,200 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
//! Contains initialization utilities for `#[pyclass]`.
use crate::exceptions::PyTypeError;
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::impl_::pyclass::PyClassBaseType;
use crate::internal::get_slot::TP_NEW;
use crate::types::{PyTuple, PyType, PyTypeMethods};
use crate::{
ffi, IntoPyObject, IntoPyObjectExt, PyClass, PyClassInitializer, PyErr, PyResult, Python,
};
use crate::{ffi::PyTypeObject, type_object::PyTypeInfo};
use core::marker::PhantomData;
/// Initializer for Python types.
///
/// This trait is used internally for distinguishing `#[pyclass]` and
/// Python native types.
pub(crate) trait PyObjectInit<T>: Sized {
/// # Safety
/// - `subtype` must be a valid pointer to a type object of T or a subclass.
unsafe fn into_new_object(
self,
py: Python<'_>,
subtype: *mut PyTypeObject,
) -> PyResult<*mut ffi::PyObject>;
}
/// Initializer for Python native types, like `PyDict`.
pub struct PyNativeTypeInitializer<T: PyTypeInfo>(pub PhantomData<T>);
impl<T: PyTypeInfo> PyObjectInit<T> for PyNativeTypeInitializer<T> {
unsafe fn into_new_object(
self,
py: Python<'_>,
subtype: *mut PyTypeObject,
) -> PyResult<*mut ffi::PyObject> {
unsafe fn inner(
py: Python<'_>,
type_ptr: *mut PyTypeObject,
subtype: *mut PyTypeObject,
) -> PyResult<*mut ffi::PyObject> {
let tp_new = unsafe {
type_ptr
.cast::<ffi::PyObject>()
.assume_borrowed_unchecked(py)
.cast_unchecked::<PyType>()
.get_slot(TP_NEW)
.ok_or_else(|| PyTypeError::new_err("base type without tp_new"))?
};
// TODO: make it possible to provide real arguments to the base tp_new
let obj =
unsafe { tp_new(subtype, PyTuple::empty(py).as_ptr(), core::ptr::null_mut()) };
if obj.is_null() {
Err(PyErr::fetch(py))
} else {
Ok(obj)
}
}
unsafe { inner(py, T::type_object_raw(py), subtype) }
}
}
pub struct TpNewValueTypeResolver<ClassT, ValueT>(TpNewTupleResolver<ClassT, ValueT>);
pub struct TpNewTupleResolver<ClassT, ValueT>(
ResolveToArbitraryObject,
PhantomData<(ClassT, ValueT)>,
);
pub struct ResolveToArbitraryObject(());
/// First step of machinery for resolving the type of `#[new]` return values, which is used to
/// implement specialization for the various cases.
///
/// Call `.resolve()` on the returned tag to get the final tag type.
///
/// This resolution step is necessary in order to encode the preference to go via `PyClassInitializer<T>`
/// for new instances of `ClassT`. Without this step, the fallback to `IntoPyObject` conflicts for
/// `ClassT` because that implementation ignores the `cls` parameter for `PyClassInit` (and would
/// therefore be incorrect when instantiating subclasses).
pub fn tp_new_resolver<ClassT, ValueT>(_: &ValueT) -> TpNewValueTypeResolver<ClassT, ValueT> {
TpNewValueTypeResolver(TpNewTupleResolver(
ResolveToArbitraryObject(()),
PhantomData,
))
}
impl<ClassT, ValueT> core::ops::Deref for TpNewValueTypeResolver<ClassT, ValueT> {
type Target = TpNewTupleResolver<ClassT, ValueT>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<ClassT, ValueT> core::ops::Deref for TpNewTupleResolver<ClassT, ValueT> {
type Target = ResolveToArbitraryObject;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// Second stage of machinery for resolving the type of `#[new]` return values. This
/// is the specialized implementation for the case where the return type should be
/// used to build a new class object (`T`, `PyClassInitializer<T>`, `(S, B)` tuples).
#[expect(private_bounds, reason = "internal trait implementation")]
impl<ClassT, ValueT> TpNewValueTypeResolver<ClassT, ValueT>
where
ClassT: PyClass,
ValueT: IntoPyClassInitializer<ClassT>,
{
pub fn resolve(&self, value: ValueT) -> PyClassInitializer<ClassT> {
value.into_pyclass_initializer()
}
}
impl<S, B> TpNewTupleResolver<S, (S, B)>
where
S: PyClass<BaseType = B>,
B: PyClass + PyClassBaseType<Initializer = PyClassInitializer<B>>,
B::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<B::BaseType>>,
{
#[deprecated(
since = "0.29.0",
note = "Tuple syntax for super class initialization is phased out. Use `PyClassInitializer` instead."
)]
pub fn resolve(&self, value: (S, B)) -> PyClassInitializer<S> {
value.into()
}
}
/// All other conversions fall back to IntoPyObject via the deref implementation
impl ResolveToArbitraryObject {
pub fn resolve<ValueT>(&self, value: ValueT) -> ValueT {
value
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be used as the return value for `#[new]` methods",
note = "all types which implement `IntoPyObject` are suitable return values for `#[new]` methods",
note = "`PyClassInitializer<T: PyClass>` may also be used and is necessary for `#[pyclass(extends = '...')]` types",
label = "must be a type which implements `IntoPyObject`, a `PyClassInitializer<{Self}>`, or a `Result<T>` wrapping such types"
)]
pub(crate) trait PyClassInit<'py, T> {
/// # Safety
/// - `cls` must be the type object for `T` (or a subclass)
unsafe fn init(
self,
cls: crate::Borrowed<'_, 'py, crate::types::PyType>,
) -> PyResult<crate::Bound<'py, crate::PyAny>>;
}
impl<'py, ClassT, T> PyClassInit<'py, ClassT> for T
where
T: IntoPyObject<'py>,
{
unsafe fn init(
self,
cls: crate::Borrowed<'_, 'py, crate::types::PyType>,
) -> PyResult<crate::Bound<'py, crate::PyAny>> {
self.into_bound_py_any(cls.py())
}
}
impl<'py, T> PyClassInit<'py, T> for PyClassInitializer<T>
where
T: PyClass,
{
unsafe fn init(
self,
cls: crate::Borrowed<'_, 'py, crate::types::PyType>,
) -> PyResult<crate::Bound<'py, crate::PyAny>> {
// SAFETY: caller has guaranteed that `cls` is correct object
unsafe { self.create_class_object_of_type(cls.py(), cls.as_type_ptr()) }
.map(crate::Bound::into_any)
}
}
/// Analogous to `Into<PyClassInitializer<T>>`, but just an internal equivalent
/// to avoid allowing user code to define custom return types from `#[new]`.
trait IntoPyClassInitializer<T: PyClass>: Sized {
fn into_pyclass_initializer(self) -> PyClassInitializer<T>;
}
impl<T: PyClass> IntoPyClassInitializer<T> for PyClassInitializer<T> {
fn into_pyclass_initializer(self) -> PyClassInitializer<T> {
self
}
}
impl<T> IntoPyClassInitializer<T> for T
where
T: PyClass,
T::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<T::BaseType>>,
{
fn into_pyclass_initializer(self) -> PyClassInitializer<T> {
self.into()
}
}
+602
View File
@@ -0,0 +1,602 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
//! Interaction with attachment of the current thread to the Python interpreter.
#[cfg(pyo3_disable_reference_pool)]
use crate::impl_::panic::PanicTrap;
use crate::{ffi, Python};
use core::cell::Cell;
#[cfg_attr(pyo3_disable_reference_pool, allow(unused_imports))]
use core::{mem, ptr::NonNull};
#[cfg(not(pyo3_disable_reference_pool))]
use std::sync::{Mutex, OnceLock};
std::thread_local! {
/// This is an internal counter in pyo3 monitoring whether this thread is attached to the interpreter.
///
/// It will be incremented whenever an AttachGuard is created, and decremented whenever
/// they are dropped.
///
/// As a result, if this thread is attached to the interpreter, ATTACH_COUNT is greater than zero.
///
/// Additionally, we sometimes need to prevent safe access to the Python interpreter,
/// e.g. when implementing `__traverse__`, which is represented by a negative value.
static ATTACH_COUNT: Cell<isize> = const { Cell::new(0) };
}
const ATTACH_FORBIDDEN_DURING_TRAVERSE: isize = -1;
/// Checks whether the thread is attached to the Python interpreter.
///
/// Note: This uses pyo3's internal count rather than PyGILState_Check for two reasons:
/// 1) for performance
/// 2) PyGILState_Check always returns 1 if the sub-interpreter APIs have ever been called,
/// which could lead to incorrect conclusions that the thread is attached.
#[inline(always)]
pub(crate) fn thread_is_attached() -> bool {
ATTACH_COUNT.try_with(|c| c.get() > 0).unwrap_or(false)
}
/// RAII type that represents thread attachment to the interpreter.
pub(crate) enum AttachGuard {
/// Indicates the thread was already attached when this AttachGuard was acquired.
Assumed,
/// Indicates that we attached when this AttachGuard was acquired
Ensured { gstate: ffi::PyGILState_STATE },
}
/// Possible error when calling `try_attach()`
pub(crate) enum AttachError {
/// Forbidden during GC traversal.
ForbiddenDuringTraverse,
/// The interpreter is not initialized.
NotInitialized,
#[cfg(Py_3_13)]
/// The interpreter is finalizing.
Finalizing,
}
impl AttachGuard {
/// PyO3 internal API for attaching to the Python interpreter. The public API is Python::attach.
///
/// If the thread was already attached via PyO3, this returns
/// `AttachGuard::Assumed`. Otherwise, the thread will attach now and
/// `AttachGuard::Ensured` will be returned.
pub(crate) fn attach() -> Self {
match Self::try_attach() {
Ok(guard) => guard,
Err(AttachError::ForbiddenDuringTraverse) => {
panic!("{}", ForbidAttaching::FORBIDDEN_DURING_TRAVERSE)
}
Err(AttachError::NotInitialized) => {
// try to initialize the interpreter and try again
crate::interpreter_lifecycle::ensure_initialized();
unsafe { Self::do_attach_unchecked() }
}
#[cfg(Py_3_13)]
Err(AttachError::Finalizing) => {
panic!("Cannot attach to the Python interpreter while it is finalizing.");
}
}
}
/// Variant of the above which will will return gracefully if the interpreter cannot be attached to.
pub(crate) fn try_attach() -> Result<Self, AttachError> {
match ATTACH_COUNT.try_with(|c| c.get()) {
Ok(i) if i > 0 => {
// SAFETY: We just checked that the thread is already attached.
return Ok(unsafe { Self::assume() });
}
// Cannot attach during GC traversal.
Ok(ATTACH_FORBIDDEN_DURING_TRAVERSE) => {
return Err(AttachError::ForbiddenDuringTraverse)
}
// other cases handled below
_ => {}
}
// SAFETY: always safe to call this
if unsafe { ffi::Py_IsInitialized() } == 0 {
return Err(AttachError::NotInitialized);
}
// Py_IsInitialized() can return 1 while Py_InitializeEx is still
// running (e.g. importing site.py). Block until any in-progress PyO3
// initialization has fully completed.
crate::interpreter_lifecycle::wait_for_initialization();
// Calling `PyGILState_Ensure` while finalizing may crash CPython in unpredictable
// ways, we'll make a best effort attempt here to avoid that. (There's a time of
// check to time-of-use issue, but it's better than nothing.)
//
// SAFETY: always safe to call this
#[cfg(Py_3_13)]
if unsafe { ffi::Py_IsFinalizing() } != 0 {
// If the interpreter is not initialized, we cannot attach.
return Err(AttachError::Finalizing);
}
// SAFETY: We have done everything reasonable to ensure we're in a safe state to
// attach to the Python interpreter.
Ok(unsafe { Self::do_attach_unchecked() })
}
/// Acquires the `AttachGuard` without performing any state checking.
///
/// This can be called in "unsafe" contexts where the normal interpreter state
/// checking performed by `AttachGuard::try_attach` may fail. This includes calling
/// as part of multi-phase interpreter initialization.
///
/// # Safety
///
/// The caller must ensure that the Python interpreter is sufficiently initialized
/// for a thread to be able to attach to it.
pub(crate) unsafe fn attach_unchecked() -> Self {
if thread_is_attached() {
return unsafe { Self::assume() };
}
unsafe { Self::do_attach_unchecked() }
}
/// Attach to the interpreter, without a fast-path to check if the thread is already attached.
#[cold]
unsafe fn do_attach_unchecked() -> Self {
// SAFETY: interpreter is sufficiently initialized to attach a thread.
let gstate = unsafe { ffi::PyGILState_Ensure() };
increment_attach_count();
// SAFETY: just attached to the interpreter
drop_deferred_references(unsafe { Python::assume_attached() });
AttachGuard::Ensured { gstate }
}
/// Acquires the `AttachGuard` while assuming that the thread is already attached
/// to the interpreter.
pub(crate) unsafe fn assume() -> Self {
increment_attach_count();
// SAFETY: invariant of calling this function
drop_deferred_references(unsafe { Python::assume_attached() });
AttachGuard::Assumed
}
/// Gets the Python token associated with this [`AttachGuard`].
#[inline]
pub(crate) fn python(&self) -> Python<'_> {
// SAFETY: this guard guarantees the thread is attached
unsafe { Python::assume_attached() }
}
}
/// The Drop implementation for `AttachGuard` will decrement the attach count (and potentially detach).
impl Drop for AttachGuard {
fn drop(&mut self) {
match self {
AttachGuard::Assumed => {}
AttachGuard::Ensured { gstate } => unsafe {
// Drop the objects in the pool before attempting to release the thread state
ffi::PyGILState_Release(*gstate);
},
}
decrement_attach_count();
}
}
#[cfg(not(pyo3_disable_reference_pool))]
type PyObjVec = Vec<NonNull<ffi::PyObject>>;
#[cfg(not(pyo3_disable_reference_pool))]
/// Thread-safe storage for objects which were dec_ref while not attached.
struct ReferencePool {
pending_decrefs: Mutex<PyObjVec>,
}
#[cfg(not(pyo3_disable_reference_pool))]
impl ReferencePool {
const fn new() -> Self {
Self {
pending_decrefs: Mutex::new(Vec::new()),
}
}
fn register_decref(&self, obj: NonNull<ffi::PyObject>) {
self.pending_decrefs.lock().unwrap().push(obj);
}
fn drop_deferred_references(&self, _py: Python<'_>) {
let mut pending_decrefs = self.pending_decrefs.lock().unwrap();
if pending_decrefs.is_empty() {
return;
}
let decrefs = mem::take(&mut *pending_decrefs);
drop(pending_decrefs);
for ptr in decrefs {
unsafe { ffi::Py_DECREF(ptr.as_ptr()) };
}
}
}
#[cfg(not(pyo3_disable_reference_pool))]
unsafe impl Send for ReferencePool {}
#[cfg(not(pyo3_disable_reference_pool))]
unsafe impl Sync for ReferencePool {}
#[cfg(not(pyo3_disable_reference_pool))]
static POOL: OnceLock<ReferencePool> = OnceLock::new();
#[cfg(not(pyo3_disable_reference_pool))]
fn get_pool() -> &'static ReferencePool {
POOL.get_or_init(ReferencePool::new)
}
#[cfg_attr(pyo3_disable_reference_pool, inline(always))]
#[cfg_attr(pyo3_disable_reference_pool, allow(unused_variables))]
fn drop_deferred_references(py: Python<'_>) {
#[cfg(not(pyo3_disable_reference_pool))]
if let Some(pool) = POOL.get() {
pool.drop_deferred_references(py);
}
}
/// A guard which can be used to temporarily detach from the interpreter and restore on `Drop`.
pub(crate) struct SuspendAttach {
count: isize,
tstate: *mut ffi::PyThreadState,
}
impl SuspendAttach {
pub(crate) unsafe fn new() -> Self {
let count = ATTACH_COUNT.with(|c| c.replace(0));
let tstate = unsafe { ffi::PyEval_SaveThread() };
Self { count, tstate }
}
}
impl Drop for SuspendAttach {
fn drop(&mut self) {
ATTACH_COUNT.with(|c| c.set(self.count));
unsafe {
ffi::PyEval_RestoreThread(self.tstate);
// Update counts of `Py<T>` that were dropped while not attached.
#[cfg(not(pyo3_disable_reference_pool))]
if let Some(pool) = POOL.get() {
pool.drop_deferred_references(Python::assume_attached());
}
}
}
}
/// Used to lock safe access to the interpreter
pub(crate) struct ForbidAttaching {
count: isize,
}
impl ForbidAttaching {
const FORBIDDEN_DURING_TRAVERSE: &'static str = "Attaching a thread to the interpreter is prohibited while a __traverse__ implementation is running.";
/// Lock access to the interpreter while an implementation of `__traverse__` is running
pub fn during_traverse() -> Self {
Self::new(ATTACH_FORBIDDEN_DURING_TRAVERSE)
}
fn new(reason: isize) -> Self {
let count = ATTACH_COUNT.with(|c| c.replace(reason));
Self { count }
}
#[cold]
fn bail(current: isize) {
match current {
ATTACH_FORBIDDEN_DURING_TRAVERSE => panic!("{}", Self::FORBIDDEN_DURING_TRAVERSE),
_ => panic!("Attaching a thread to the interpreter is currently prohibited."),
}
}
}
impl Drop for ForbidAttaching {
fn drop(&mut self) {
ATTACH_COUNT.with(|c| c.set(self.count));
}
}
/// Registers a Python object pointer inside the release pool, to have its reference count decreased
/// the next time the thread is attached in pyo3.
///
/// If the thread is attached, the reference count will be decreased immediately instead of being queued
/// for later.
///
/// # Safety
/// - The object must be an owned Python reference.
/// - The reference must not be used after calling this function.
#[inline]
pub unsafe fn register_decref(obj: NonNull<ffi::PyObject>) {
#[cfg(not(pyo3_disable_reference_pool))]
{
get_pool().register_decref(obj);
}
#[cfg(all(
pyo3_disable_reference_pool,
not(pyo3_leak_on_drop_without_reference_pool)
))]
{
let _trap = PanicTrap::new("Aborting the process to avoid panic-from-drop.");
panic!("Cannot drop pointer into Python heap without the thread being attached.");
}
}
/// Private helper function to check if we are currently in a GC traversal (as detected by PyO3).
#[cfg(any(not(Py_LIMITED_API), Py_3_11))]
pub(crate) fn is_in_gc_traversal() -> bool {
ATTACH_COUNT
.try_with(|c| c.get() == ATTACH_FORBIDDEN_DURING_TRAVERSE)
.unwrap_or(false)
}
/// Increments pyo3's internal attach count - to be called whenever an AttachGuard is created.
#[inline(always)]
fn increment_attach_count() {
// Ignores the error in case this function called from `atexit`.
let _ = ATTACH_COUNT.try_with(|c| {
let current = c.get();
if current < 0 {
ForbidAttaching::bail(current);
}
c.set(current + 1);
});
}
/// Decrements pyo3's internal attach count - to be called whenever AttachGuard is dropped.
#[inline(always)]
fn decrement_attach_count() {
// Ignores the error in case this function called from `atexit`.
let _ = ATTACH_COUNT.try_with(|c| {
let current = c.get();
debug_assert!(
current > 0,
"Negative attach count detected. Please report this error to the PyO3 repo as a bug."
);
c.set(current - 1);
});
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Py, PyAny, Python};
fn get_object(py: Python<'_>) -> Py<PyAny> {
py.eval(c"object()", None, None).unwrap().unbind()
}
#[cfg(not(pyo3_disable_reference_pool))]
fn pool_dec_refs_does_not_contain(obj: &Py<PyAny>) -> bool {
!get_pool()
.pending_decrefs
.lock()
.unwrap()
.contains(&unsafe { NonNull::new_unchecked(obj.as_ptr()) })
}
// With free-threading, threads can empty the POOL at any time, so this
// function does not test anything meaningful
#[cfg(not(any(pyo3_disable_reference_pool, Py_GIL_DISABLED)))]
fn pool_dec_refs_contains(obj: &Py<PyAny>) -> bool {
get_pool()
.pending_decrefs
.lock()
.unwrap()
.contains(&unsafe { NonNull::new_unchecked(obj.as_ptr()) })
}
#[test]
fn test_pyobject_drop_attached_decreases_refcnt() {
Python::attach(|py| {
let obj = get_object(py);
// Create a reference to drop while attached.
let reference = obj.clone_ref(py);
assert_eq!(obj._get_refcnt(py), 2);
#[cfg(not(pyo3_disable_reference_pool))]
assert!(pool_dec_refs_does_not_contain(&obj));
// While attached, reference count will be decreased immediately.
drop(reference);
assert_eq!(obj._get_refcnt(py), 1);
#[cfg(not(any(pyo3_disable_reference_pool)))]
assert!(pool_dec_refs_does_not_contain(&obj));
});
}
#[test]
#[cfg(all(not(pyo3_disable_reference_pool), not(target_arch = "wasm32")))] // We are building wasm Python with pthreads disabled
fn test_pyobject_drop_detached_doesnt_decrease_refcnt() {
let obj = Python::attach(|py| {
let obj = get_object(py);
// Create a reference to drop while detached.
let reference = obj.clone_ref(py);
assert_eq!(obj._get_refcnt(py), 2);
assert!(pool_dec_refs_does_not_contain(&obj));
// Drop reference in a separate (detached) thread.
std::thread::spawn(move || drop(reference)).join().unwrap();
// The reference count should not have changed, it is remembered
// to release later.
assert_eq!(obj._get_refcnt(py), 2);
#[cfg(not(Py_GIL_DISABLED))]
assert!(pool_dec_refs_contains(&obj));
obj
});
// On next attach, the reference is released
#[allow(unused)]
Python::attach(|py| {
// With free-threading, another thread could still be processing
// DECREFs after releasing the lock on the POOL, so the
// refcnt could still be 2 when this assert happens
#[cfg(not(Py_GIL_DISABLED))]
assert_eq!(obj._get_refcnt(py), 1);
assert!(pool_dec_refs_does_not_contain(&obj));
});
}
#[test]
fn test_attach_counts() {
// Check `attach` and AttachGuard both increase counts correctly
let get_attach_count = || ATTACH_COUNT.with(|c| c.get());
assert_eq!(get_attach_count(), 0);
Python::attach(|_| {
assert_eq!(get_attach_count(), 1);
let pool = unsafe { AttachGuard::assume() };
assert_eq!(get_attach_count(), 2);
let pool2 = unsafe { AttachGuard::assume() };
assert_eq!(get_attach_count(), 3);
drop(pool);
assert_eq!(get_attach_count(), 2);
Python::attach(|_| {
// nested `attach` updates attach count
assert_eq!(get_attach_count(), 3);
});
assert_eq!(get_attach_count(), 2);
drop(pool2);
assert_eq!(get_attach_count(), 1);
});
assert_eq!(get_attach_count(), 0);
}
#[test]
fn test_detach() {
assert!(!thread_is_attached());
Python::attach(|py| {
assert!(thread_is_attached());
py.detach(move || {
assert!(!thread_is_attached());
Python::attach(|_| assert!(thread_is_attached()));
assert!(!thread_is_attached());
});
assert!(thread_is_attached());
});
assert!(!thread_is_attached());
}
#[cfg(feature = "py-clone")]
#[test]
#[should_panic]
fn test_detach_updates_refcounts() {
Python::attach(|py| {
// Make a simple object with 1 reference
let obj = get_object(py);
assert_eq!(obj._get_refcnt(py), 1);
// Cloning the object when detached should panic
py.detach(|| obj.clone());
});
}
#[test]
fn recursive_attach_ok() {
Python::attach(|py| {
let obj = Python::attach(|_| py.eval(c"object()", None, None).unwrap());
assert_eq!(obj._get_refcnt(), 1);
})
}
#[cfg(feature = "py-clone")]
#[test]
fn test_clone_attached() {
Python::attach(|py| {
let obj = get_object(py);
let count = obj._get_refcnt(py);
// Cloning when attached should increase reference count immediately
#[expect(clippy::redundant_clone)]
let c = obj.clone();
assert_eq!(count + 1, c._get_refcnt(py));
})
}
#[test]
#[cfg(not(pyo3_disable_reference_pool))]
fn test_drop_deferred_references_does_not_deadlock() {
// drop_deferred_references can run arbitrary Python code during Py_DECREF.
// if the locking is implemented incorrectly, it will deadlock.
use crate::ffi;
Python::attach(|py| {
let obj = get_object(py);
unsafe extern "C" fn capsule_drop(capsule: *mut ffi::PyObject) {
// This line will implicitly call drop_deferred_references
// -> and so cause deadlock if drop_deferred_references is not handling recursion correctly.
let pool = unsafe { AttachGuard::assume() };
// Rebuild obj so that it can be dropped
unsafe {
use crate::Bound;
Bound::from_owned_ptr(
pool.python(),
ffi::PyCapsule_GetPointer(capsule, core::ptr::null()) as _,
)
};
}
let ptr = obj.into_ptr();
let capsule =
unsafe { ffi::PyCapsule_New(ptr as _, core::ptr::null(), Some(capsule_drop)) };
get_pool().register_decref(NonNull::new(capsule).unwrap());
// Updating the counts will call decref on the capsule, which calls capsule_drop
get_pool().drop_deferred_references(py);
})
}
#[test]
#[cfg(not(pyo3_disable_reference_pool))]
fn test_attach_guard_drop_deferred_references() {
Python::attach(|py| {
let obj = get_object(py);
// For AttachGuard::attach
get_pool().register_decref(NonNull::new(obj.clone_ref(py).into_ptr()).unwrap());
#[cfg(not(Py_GIL_DISABLED))]
assert!(pool_dec_refs_contains(&obj));
let _guard = AttachGuard::attach();
assert!(pool_dec_refs_does_not_contain(&obj));
// For AttachGuard::assume
get_pool().register_decref(NonNull::new(obj.clone_ref(py).into_ptr()).unwrap());
#[cfg(not(Py_GIL_DISABLED))]
assert!(pool_dec_refs_contains(&obj));
let _guard2 = unsafe { AttachGuard::assume() };
assert!(pool_dec_refs_does_not_contain(&obj));
})
}
}
+64
View File
@@ -0,0 +1,64 @@
use core::ptr::NonNull;
use crate::ffi::{self, Py_ssize_t, PY_SSIZE_T_MAX};
macro_rules! pyo3_exception {
($doc: expr, $name: ident, $base: ty) => {
#[doc = $doc]
#[repr(transparent)]
pub struct $name($crate::PyAny);
$crate::impl_exception_boilerplate!($name);
$crate::create_exception_type_object!(pyo3_runtime, $name, $base, Some($doc));
};
}
/// Convert an usize index into a Py_ssize_t index, clamping overflow to
/// PY_SSIZE_T_MAX.
pub(crate) fn get_ssize_index(index: usize) -> Py_ssize_t {
index.min(PY_SSIZE_T_MAX as usize) as Py_ssize_t
}
// TODO: use ptr::fn_addr_eq on MSRV 1.85
pub(crate) fn clear_eq(f: Option<ffi::inquiry>, g: ffi::inquiry) -> bool {
#[cfg(fn_ptr_eq)]
#[expect(clippy::incompatible_msrv, reason = "guarded by cfg(fn_ptr_eq)")]
{
let Some(f) = f else { return false };
core::ptr::fn_addr_eq(f, g)
}
#[cfg(not(fn_ptr_eq))]
{
f == Some(g)
}
}
// TODO: use ptr::fn_addr_eq on MSRV 1.85
pub(crate) fn traverse_eq(f: Option<ffi::traverseproc>, g: ffi::traverseproc) -> bool {
#[cfg(fn_ptr_eq)]
#[expect(clippy::incompatible_msrv, reason = "guarded by cfg(fn_ptr_eq)")]
{
let Some(f) = f else { return false };
core::ptr::fn_addr_eq(f, g)
}
#[cfg(not(fn_ptr_eq))]
{
f == Some(g)
}
}
// TODO: use Box::into_non_null when stabilized
pub(crate) fn box_into_non_null<T>(b: Box<T>) -> NonNull<T> {
// SAFETY: `Box::into_raw` guarantees an non-null pointer
unsafe { NonNull::new_unchecked(Box::into_raw(b)) }
}
/// Replacement for the unstable `<*mut [T; N]>::as_mut_ptr` method, which avoids
/// possibility of type getting lost from using e.g. `.cast()` to change array
/// type to point to the data.
pub(crate) const fn array_ptr_as_mut<T, const N: usize>(ptr: *mut [T; N]) -> *mut T {
ptr.cast()
}
+144
View File
@@ -0,0 +1,144 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
#[cfg(not(any(PyPy, GraalPy)))]
use crate::{ffi, internal::state::AttachGuard, Python};
static START: std::sync::Once = std::sync::Once::new();
#[cfg(not(any(PyPy, GraalPy)))]
pub(crate) fn initialize() {
// Protect against race conditions when Python is not yet initialized and multiple threads
// concurrently call 'initialize()'. Note that we do not protect against
// concurrent initialization of the Python runtime by other users of the Python C API.
START.call_once_force(|_| unsafe {
// Use call_once_force because if initialization panics, it's okay to try again.
if ffi::Py_IsInitialized() == 0 {
ffi::Py_InitializeEx(0);
// Release the GIL.
ffi::PyEval_SaveThread();
}
});
}
/// Executes the provided closure with an embedded Python interpreter.
///
/// This function initializes the Python interpreter, executes the provided closure, and then
/// finalizes the Python interpreter.
///
/// After execution all Python resources are cleaned up, and no further Python APIs can be called.
/// Because many Python modules implemented in C do not support multiple Python interpreters in a
/// single process, it is not safe to call this function more than once. (Many such modules will not
/// initialize correctly on the second run.)
///
/// # Panics
/// - If the Python interpreter is already initialized before calling this function.
///
/// # Safety
/// - This function should only ever be called once per process (usually as part of the `main`
/// function). It is also not thread-safe.
/// - No Python APIs can be used after this function has finished executing.
/// - The return value of the closure must not contain any Python value, _including_ `PyResult`.
///
/// # Examples
///
/// ```rust
/// unsafe {
/// pyo3::with_embedded_python_interpreter(|py| {
/// if let Err(e) = py.run(c"print('Hello World')", None, None) {
/// // We must make sure to not return a `PyErr`!
/// e.print(py);
/// }
/// });
/// }
/// ```
#[cfg(not(any(PyPy, GraalPy)))]
pub unsafe fn with_embedded_python_interpreter<F, R>(f: F) -> R
where
F: for<'p> FnOnce(Python<'p>) -> R,
{
assert_eq!(
unsafe { ffi::Py_IsInitialized() },
0,
"called `with_embedded_python_interpreter` but a Python interpreter is already running."
);
unsafe { ffi::Py_InitializeEx(0) };
let result = {
let guard = unsafe { AttachGuard::assume() };
let py = guard.python();
// Import the threading module - this ensures that it will associate this thread as the "main"
// thread, which is important to avoid an `AssertionError` at finalization.
py.import("threading").unwrap();
// Execute the closure.
f(py)
};
// Finalize the Python interpreter.
unsafe { ffi::Py_Finalize() };
result
}
/// If PyO3 is currently running `Py_InitializeEx` inside the `Once` guard,
/// block until it completes. Needed because `Py_InitializeEx` sets the
/// `initialized` flag in the interpreter to true before it finishes all its
/// steps (in particular, before it imports `site.py`).
///
/// This must only be called after `Py_IsInitialized()` has returned true.
///
/// If the `Once` was never started (e.g. the interpreter was initialized
/// externally, not through PyO3), `call_once` runs the empty closure and
/// returns — this is fine because `initialize()` checks
/// `Py_IsInitialized()` inside its closure and skips `Py_InitializeEx` if
/// the interpreter is already running. If the `Once` is currently in
/// progress (another thread is inside `initialize()`), `call_once` blocks
/// until it completes.
pub(crate) fn wait_for_initialization() {
// TODO: use START.wait_force() on MSRV 1.86
// TODO: may not be needed on Python 3.15 (https://github.com/python/cpython/pull/146303)
START.call_once(|| {
assert_ne!(unsafe { crate::ffi::Py_IsInitialized() }, 0);
});
}
pub(crate) fn ensure_initialized() {
// Maybe auto-initialize the interpreter:
// - If auto-initialize feature set and supported, try to initialize the interpreter.
// - If the auto-initialize feature is set but unsupported, emit hard errors only when the
// extension-module feature is not activated - extension modules don't care about
// auto-initialize so this avoids breaking existing builds.
// - Otherwise, just check the interpreter is initialized.
#[cfg(all(feature = "auto-initialize", not(any(PyPy, GraalPy))))]
{
initialize();
}
#[cfg(not(all(feature = "auto-initialize", not(any(PyPy, GraalPy)))))]
{
// This is a "hack" to make running `cargo test` for PyO3 convenient (i.e. no need
// to specify `--features auto-initialize` manually). Tests within the crate itself
// all depend on the auto-initialize feature for conciseness but Cargo does not
// provide a mechanism to specify required features for tests.
#[cfg(not(any(PyPy, GraalPy)))]
if option_env!("CARGO_PRIMARY_PACKAGE").is_some() {
initialize();
}
START.call_once_force(|_| unsafe {
// Use call_once_force because if there is a panic because the interpreter is
// not initialized, it's fine for the user to initialize the interpreter and
// retry.
assert_ne!(
crate::ffi::Py_IsInitialized(),
0,
"The Python interpreter is not initialized and the `auto-initialize` \
feature is not enabled.\n\n\
Consider calling `Python::initialize()` before attempting \
to use Python APIs."
);
});
}
}
+531
View File
@@ -0,0 +1,531 @@
#![warn(
clippy::alloc_instead_of_core,
clippy::std_instead_of_alloc,
clippy::std_instead_of_core
)]
#![warn(missing_docs)]
#![cfg_attr(
feature = "nightly",
feature(auto_traits, negative_impls, iter_advance_by)
)]
#![cfg_attr(all(feature = "nightly", Py_GIL_DISABLED), feature(try_trait_v2))]
#![cfg_attr(docsrs, feature(doc_cfg))]
// Deny some lints in doctests.
// Use `#[allow(...)]` locally to override.
#![doc(test(attr(
deny(
rust_2018_idioms,
unused_lifetimes,
rust_2021_prelude_collisions,
warnings
),
allow(
unused_imports, // to make imports already in the prelude explicit
unused_variables,
unused_assignments,
unused_extern_crates,
// FIXME https://github.com/rust-lang/rust/issues/121621#issuecomment-1965156376
unknown_lints,
non_local_definitions,
)
)))]
//! Rust bindings to the Python interpreter.
//!
//! PyO3 can be used to write native Python modules or run Python code and modules from Rust.
//!
//! See [the guide] for a detailed introduction.
//!
//! # PyO3's object types
//!
//! PyO3 has several core types that you should familiarize yourself with:
//!
//! ## The `Python<'py>` object, and the `'py` lifetime
//!
//! Holding the [global interpreter lock] (GIL) is modeled with the [`Python<'py>`](Python) token. Many
//! Python APIs require that the GIL is held, and PyO3 uses this token as proof that these APIs
//! can be called safely. It can be explicitly acquired and is also implicitly acquired by PyO3
//! as it wraps Rust functions and structs into Python functions and objects.
//!
//! The [`Python<'py>`](Python) token's lifetime `'py` is common to many PyO3 APIs:
//! - Types that also have the `'py` lifetime, such as the [`Bound<'py, T>`](Bound) smart pointer, are
//! bound to the Python GIL and rely on this to offer their functionality. These types often
//! have a [`.py()`](Bound::py) method to get the associated [`Python<'py>`](Python) token.
//! - Functions which depend on the `'py` lifetime, such as [`PyList::new`](types::PyList::new),
//! require a [`Python<'py>`](Python) token as an input. Sometimes the token is passed implicitly by
//! taking a [`Bound<'py, T>`](Bound) or other type which is bound to the `'py` lifetime.
//! - Traits which depend on the `'py` lifetime, such as [`FromPyObject<'py>`](FromPyObject), usually have
//! inputs or outputs which depend on the lifetime. Adding the lifetime to the trait allows
//! these inputs and outputs to express their binding to the GIL in the Rust type system.
//!
//! ## Python object smart pointers
//!
//! PyO3 has two core smart pointers to refer to Python objects, [`Py<T>`](Py) and its GIL-bound
//! form [`Bound<'py, T>`](Bound) which carries the `'py` lifetime. (There is also
//! [`Borrowed<'a, 'py, T>`](instance::Borrowed), but it is used much more rarely).
//!
//! The type parameter `T` in these smart pointers can be filled by:
//! - [`PyAny`], e.g. `Py<PyAny>` or `Bound<'py, PyAny>`, where the Python object type is not
//! known.
//! - Concrete Python types like [`PyList`](types::PyList) or [`PyTuple`](types::PyTuple).
//! - Rust types which are exposed to Python using the [`#[pyclass]`](macro@pyclass) macro.
//!
//! See the [guide][types] for an explanation of the different Python object types.
//!
//! ## PyErr
//!
//! The vast majority of operations in this library will return [`PyResult<...>`](PyResult).
//! This is an alias for the type `Result<..., PyErr>`.
//!
//! A `PyErr` represents a Python exception. A `PyErr` returned to Python code will be raised as a
//! Python exception. Errors from `PyO3` itself are also exposed as Python exceptions.
//!
//! # Feature flags
//!
//! PyO3 uses [feature flags] to enable you to opt-in to additional functionality. For a detailed
//! description, see the [Features chapter of the guide].
//!
//! ## Default feature flags
//!
//! The following features are turned on by default:
//! - `macros`: Enables various macros, including all the attribute macros.
//!
//! ## Optional feature flags
//!
//! The following features customize PyO3's behavior:
//!
//! - `abi3`: Restricts PyO3's API to a subset of the full Python API which is guaranteed by
//! [PEP 384] to be forward-compatible with future Python versions.
//! - `auto-initialize`: Changes [`Python::attach`] to automatically initialize the Python
//! interpreter if needed.
//! - `multiple-pymethods`: Enables the use of multiple [`#[pymethods]`](macro@crate::pymethods)
//! blocks per [`#[pyclass]`](macro@crate::pyclass). This adds a dependency on the [inventory]
//! crate, which is not supported on all platforms.
//!
//! The following features enable interactions with other crates in the Rust ecosystem:
//! - [`anyhow`]: Enables a conversion from [anyhow]’s [`Error`][anyhow_error] type to [`PyErr`].
//! - [`chrono`]: Enables a conversion from [chrono]'s structures to the equivalent Python ones.
//! - [`chrono-tz`]: Enables a conversion from [chrono-tz]'s `Tz` enum. Requires Python 3.9+.
//! - [`either`]: Enables conversions between Python objects and [either]'s [`Either`] type.
//! - [`eyre`]: Enables a conversion from [eyre]’s [`Report`] type to [`PyErr`].
//! - [`hashbrown`]: Enables conversions between Python objects and [hashbrown]'s [`HashMap`] and
//! [`HashSet`] types.
//! - [`indexmap`][indexmap_feature]: Enables conversions between Python dictionary and [indexmap]'s [`IndexMap`].
//! - [`num-bigint`]: Enables conversions between Python objects and [num-bigint]'s [`BigInt`] and
//! [`BigUint`] types.
//! - [`num-complex`]: Enables conversions between Python objects and [num-complex]'s [`Complex`]
//! type.
//! - [`num-rational`]: Enables conversions between Python's fractions.Fraction and [num-rational]'s types
//! - [`ordered-float`]: Enables conversions between Python's float and [ordered-float]'s types
//! - [`rust_decimal`]: Enables conversions between Python's decimal.Decimal and [rust_decimal]'s
//! [`Decimal`] type.
//! - [`serde`]: Allows implementing [serde]'s [`Serialize`] and [`Deserialize`] traits for
//! [`Py`]`<T>` for all `T` that implement [`Serialize`] and [`Deserialize`].
//! - [`smallvec`][smallvec]: Enables conversions between Python list and [smallvec]'s [`SmallVec`].
//!
//! ## Unstable features
//!
//! - `nightly`: Uses `#![feature(auto_traits, negative_impls)]` to define [`Ungil`] as an auto trait.
//
//! ## `rustc` environment flags
//! - `Py_3_8`, `Py_3_9`, `Py_3_10`, `Py_3_11`, `Py_3_12`, `Py_3_13`, `Py_3_14`: Marks code that is
//! only enabled when compiling for a given minimum Python version.
//! - `Py_LIMITED_API`: Marks code enabled when the `abi3` feature flag is enabled.
//! - `Py_GIL_DISABLED`: Marks code that runs only in the free-threaded build of CPython.
//! - `PyPy` - Marks code enabled when compiling for PyPy.
//! - `GraalPy` - Marks code enabled when compiling for GraalPy.
//!
//! Additionally, you can query for the values `Py_DEBUG`, `Py_REF_DEBUG`,
//! `Py_TRACE_REFS`, and `COUNT_ALLOCS` from `py_sys_config` to query for the
//! corresponding C build-time defines. For example, to conditionally define
//! debug code using `Py_DEBUG`, you could do:
//!
//! ```rust,ignore
//! #[cfg(py_sys_config = "Py_DEBUG")]
//! println!("only runs if python was compiled with Py_DEBUG")
//! ```
//! To use these attributes, add [`pyo3-build-config`] as a build dependency in
//! your `Cargo.toml` and call `pyo3_build_config::use_pyo3_cfgs()` in a
//! `build.rs` file.
//!
//! # Minimum supported Rust and Python versions
//!
//! Requires Rust 1.83 or greater.
//!
//! PyO3 supports the following Python distributions:
//! - CPython 3.8 or greater
//! - PyPy 7.3 (Python 3.11+)
//! - GraalPy 24.0 or greater (Python 3.10+)
//!
//! # Example: Building a native Python module
//!
//! PyO3 can be used to generate a native Python module. The easiest way to try this out for the
//! first time is to use [`maturin`]. `maturin` is a tool for building and publishing Rust-based
//! Python packages with minimal configuration. The following steps set up some files for an example
//! Python module, install `maturin`, and then show how to build and import the Python module.
//!
//! First, create a new folder (let's call it `string_sum`) containing the following two files:
//!
//! **`Cargo.toml`**
//!
//! ```toml
//! [package]
//! name = "string-sum"
//! version = "0.1.0"
//! edition = "2021"
//!
//! [lib]
//! name = "string_sum"
//! # "cdylib" is necessary to produce a shared library for Python to import from.
//! #
//! # Downstream Rust code (including code in `bin/`, `examples/`, and `tests/`) will not be able
//! # to `use string_sum;` unless the "rlib" or "lib" crate type is also included, e.g.:
//! # crate-type = ["cdylib", "rlib"]
//! crate-type = ["cdylib"]
//!
//! [dependencies]
#![doc = concat!("pyo3 = \"", env!("CARGO_PKG_VERSION"), "\"")]
//! ```
//!
//! **`src/lib.rs`**
//! ```rust,no_run
//! use pyo3::prelude::*;
//!
//! /// Formats the sum of two numbers as string.
//! #[pyfunction]
//! fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
//! Ok((a + b).to_string())
//! }
//!
//! /// A Python module implemented in Rust.
//! #[pymodule]
//! fn string_sum(m: &Bound<'_, PyModule>) -> PyResult<()> {
//! m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
//!
//! Ok(())
//! }
//! ```
//!
//! With those two files in place, now `maturin` needs to be installed. This can be done using
//! Python's package manager `pip`. First, load up a new Python `virtualenv`, and install `maturin`
//! into it:
//! ```bash
//! $ cd string_sum
//! $ python -m venv .env
//! $ source .env/bin/activate
//! $ pip install maturin
//! ```
//!
//! Now build and execute the module:
//! ```bash
//! $ maturin develop
//! # lots of progress output as maturin runs the compilation...
//! $ python
//! >>> import string_sum
//! >>> string_sum.sum_as_string(5, 20)
//! '25'
//! ```
//!
//! As well as with `maturin`, it is possible to build using [setuptools-rust] or
//! [manually][manual_builds]. Both offer more flexibility than `maturin` but require further
//! configuration.
//!
//! # Example: Using Python from Rust
//!
//! To embed Python into a Rust binary, you need to ensure that your Python installation contains a
//! shared library. The following steps demonstrate how to ensure this (for Ubuntu), and then give
//! some example code which runs an embedded Python interpreter.
//!
//! To install the Python shared library on Ubuntu:
//! ```bash
//! sudo apt install python3-dev
//! ```
//!
//! Start a new project with `cargo new` and add `pyo3` to the `Cargo.toml` like this:
//! ```toml
//! [dependencies.pyo3]
#![doc = concat!("version = \"", env!("CARGO_PKG_VERSION"), "\"")]
//! # this is necessary to automatically initialize the Python interpreter
//! features = ["auto-initialize"]
//! ```
//!
//! Example program displaying the value of `sys.version` and the current user name:
//! ```rust
//! use pyo3::prelude::*;
//! use pyo3::types::IntoPyDict;
//!
//! fn main() -> PyResult<()> {
//! Python::attach(|py| {
//! let sys = py.import("sys")?;
//! let version: String = sys.getattr("version")?.extract()?;
//!
//! let locals = [("os", py.import("os")?)].into_py_dict(py)?;
//! let code = c"os.getenv('USER') or os.getenv('USERNAME') or 'Unknown'";
//! let user: String = py.eval(code, None, Some(&locals))?.extract()?;
//!
//! println!("Hello {}, I'm Python {}", user, version);
//! Ok(())
//! })
//! }
//! ```
//!
//! The guide has [a section][calling_rust] with lots of examples about this topic.
//!
//! # Other Examples
//!
//! The PyO3 [README](https://github.com/PyO3/pyo3#readme) contains quick-start examples for both
//! using [Rust from Python] and [Python from Rust].
//!
//! The PyO3 repository's [examples subdirectory]
//! contains some basic packages to demonstrate usage of PyO3.
//!
//! There are many projects using PyO3 - see a list of some at
//! <https://github.com/PyO3/pyo3#examples>.
//!
//! [anyhow]: https://docs.rs/anyhow/ "A trait object based error system for easy idiomatic error handling in Rust applications."
//! [anyhow_error]: https://docs.rs/anyhow/latest/anyhow/struct.Error.html "Anyhows `Error` type, a wrapper around a dynamic error type"
//! [`anyhow`]: ./anyhow/index.html "Documentation about the `anyhow` feature."
//! [inventory]: https://docs.rs/inventory
//! [`HashMap`]: https://docs.rs/hashbrown/latest/hashbrown/struct.HashMap.html
//! [`HashSet`]: https://docs.rs/hashbrown/latest/hashbrown/struct.HashSet.html
//! [`SmallVec`]: https://docs.rs/smallvec/latest/smallvec/struct.SmallVec.html
//! [`Uuid`]: https://docs.rs/uuid/latest/uuid/struct.Uuid.html
//! [`IndexMap`]: https://docs.rs/indexmap/latest/indexmap/map/struct.IndexMap.html
//! [`BigInt`]: https://docs.rs/num-bigint/latest/num_bigint/struct.BigInt.html
//! [`BigUint`]: https://docs.rs/num-bigint/latest/num_bigint/struct.BigUint.html
//! [`Complex`]: https://docs.rs/num-complex/latest/num_complex/struct.Complex.html
//! [`Deserialize`]: https://docs.rs/serde/latest/serde/trait.Deserialize.html
//! [`Serialize`]: https://docs.rs/serde/latest/serde/trait.Serialize.html
//! [chrono]: https://docs.rs/chrono/ "Date and Time for Rust."
//! [chrono-tz]: https://docs.rs/chrono-tz/ "TimeZone implementations for chrono from the IANA database."
//! [`chrono`]: ./chrono/index.html "Documentation about the `chrono` feature."
//! [`chrono-tz`]: ./chrono-tz/index.html "Documentation about the `chrono-tz` feature."
//! [either]: https://docs.rs/either/ "A type that represents one of two alternatives."
//! [`either`]: ./either/index.html "Documentation about the `either` feature."
//! [`Either`]: https://docs.rs/either/latest/either/enum.Either.html
//! [eyre]: https://docs.rs/eyre/ "A library for easy idiomatic error handling and reporting in Rust applications."
//! [`Report`]: https://docs.rs/eyre/latest/eyre/struct.Report.html
//! [`eyre`]: ./eyre/index.html "Documentation about the `eyre` feature."
//! [`hashbrown`]: ./hashbrown/index.html "Documentation about the `hashbrown` feature."
//! [indexmap_feature]: ./indexmap/index.html "Documentation about the `indexmap` feature."
//! [`maturin`]: https://github.com/PyO3/maturin "Build and publish crates with pyo3, rust-cpython and cffi bindings as well as rust binaries as python packages"
//! [`num-bigint`]: ./num_bigint/index.html "Documentation about the `num-bigint` feature."
//! [`num-complex`]: ./num_complex/index.html "Documentation about the `num-complex` feature."
//! [`num-rational`]: ./num_rational/index.html "Documentation about the `num-rational` feature."
//! [`ordered-float`]: ./ordered_float/index.html "Documentation about the `ordered-float` feature."
//! [`pyo3-build-config`]: https://docs.rs/pyo3-build-config
//! [rust_decimal]: https://docs.rs/rust_decimal
//! [`rust_decimal`]: ./rust_decimal/index.html "Documentation about the `rust_decimal` feature."
//! [`Decimal`]: https://docs.rs/rust_decimal/latest/rust_decimal/struct.Decimal.html
//! [`serde`]: <./serde/index.html> "Documentation about the `serde` feature."
#![doc = concat!("[calling_rust]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/python-from-rust.html \"Calling Python from Rust - PyO3 user guide\"")]
//! [examples subdirectory]: https://github.com/PyO3/pyo3/tree/main/examples
//! [feature flags]: https://doc.rust-lang.org/cargo/reference/features.html "Features - The Cargo Book"
//! [global interpreter lock]: https://docs.python.org/3/glossary.html#term-global-interpreter-lock
//! [hashbrown]: https://docs.rs/hashbrown
//! [smallvec]: https://docs.rs/smallvec
//! [uuid]: https://docs.rs/uuid
//! [indexmap]: https://docs.rs/indexmap
#![doc = concat!("[manual_builds]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/building-and-distribution.html#manual-builds \"Manual builds - Building and Distribution - PyO3 user guide\"")]
//! [num-bigint]: https://docs.rs/num-bigint
//! [num-complex]: https://docs.rs/num-complex
//! [num-rational]: https://docs.rs/num-rational
//! [ordered-float]: https://docs.rs/ordered-float
//! [serde]: https://docs.rs/serde
//! [setuptools-rust]: https://github.com/PyO3/setuptools-rust "Setuptools plugin for Rust extensions"
//! [the guide]: https://pyo3.rs "PyO3 user guide"
#![doc = concat!("[types]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/types.html \"GIL lifetimes, mutability and Python object types\"")]
//! [PEP 384]: https://www.python.org/dev/peps/pep-0384 "PEP 384 -- Defining a Stable ABI"
//! [Python from Rust]: https://github.com/PyO3/pyo3#using-python-from-rust
//! [Rust from Python]: https://github.com/PyO3/pyo3#using-rust-from-python
#![doc = concat!("[Features chapter of the guide]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/features.html#features-reference \"Features Reference - PyO3 user guide\"")]
//! [`Ungil`]: crate::marker::Ungil
extern crate alloc;
pub use crate::class::*;
pub use crate::conversion::{FromPyObject, IntoPyObject, IntoPyObjectExt};
pub use crate::err::{CastError, CastIntoError, PyErr, PyErrArguments, PyResult, ToPyErr};
pub use crate::instance::{Borrowed, Bound, BoundObject, Py};
#[cfg(not(any(PyPy, GraalPy)))]
pub use crate::interpreter_lifecycle::with_embedded_python_interpreter;
pub use crate::marker::Python;
pub use crate::pycell::{PyRef, PyRefMut};
pub use crate::pyclass::{PyClass, PyClassGuard, PyClassGuardMut};
pub use crate::pyclass_init::PyClassInitializer;
pub use crate::type_object::{PyTypeCheck, PyTypeInfo};
pub use crate::types::PyAny;
pub use crate::version::PythonVersionInfo;
pub(crate) mod ffi_ptr_ext;
pub(crate) mod py_result_ext;
pub(crate) mod sealed;
mod platform;
/// Old module which contained some implementation details of the `#[pyproto]` module.
///
/// Prefer using the same content from `pyo3::pyclass`, e.g. `use pyo3::pyclass::CompareOp` instead
/// of `use pyo3::class::basic::CompareOp`.
///
/// For compatibility reasons this has not yet been removed, however will be done so
/// once <https://github.com/rust-lang/rust/issues/30827> is resolved.
pub mod class {
pub use self::gc::{PyTraverseError, PyVisit};
/// Old module which contained some implementation details of the `#[pyproto]` module.
///
/// Prefer using the same content from `pyo3::pyclass`, e.g. `use pyo3::pyclass::CompareOp` instead
/// of `use pyo3::class::basic::CompareOp`.
///
/// For compatibility reasons this has not yet been removed, however will be done so
/// once <https://github.com/rust-lang/rust/issues/30827> is resolved.
pub mod basic {
pub use crate::pyclass::CompareOp;
}
/// Old module which contained some implementation details of the `#[pyproto]` module.
///
/// Prefer using the same content from `pyo3::pyclass`, e.g. `use pyo3::pyclass::PyTraverseError` instead
/// of `use pyo3::class::gc::PyTraverseError`.
///
/// For compatibility reasons this has not yet been removed, however will be done so
/// once <https://github.com/rust-lang/rust/issues/30827> is resolved.
pub mod gc {
pub use crate::pyclass::{PyTraverseError, PyVisit};
}
}
#[cfg(all(feature = "macros", feature = "multiple-pymethods"))]
#[doc(hidden)]
pub use inventory; // Re-exported for `#[pyclass]` and `#[pymethods]` with `multiple-pymethods`.
/// Tests and helpers which reside inside PyO3's main library. Declared first so that macros
/// are available in unit tests.
#[cfg(test)]
mod test_utils;
#[cfg(test)]
mod tests;
#[macro_use]
mod internal;
#[macro_use]
mod internal_tricks;
// Macro dependencies, also contains macros exported for use across the codebase and
// in expanded macros.
#[doc(hidden)]
pub mod impl_;
pub mod buffer;
pub mod call;
pub mod conversion;
mod conversions;
#[cfg(feature = "experimental-async")]
pub mod coroutine;
mod err;
pub mod exceptions;
pub mod ffi;
pub(crate) mod fmt;
mod instance;
mod interpreter_lifecycle;
pub mod marker;
pub mod marshal;
#[macro_use]
pub mod sync;
pub(crate) mod byteswriter;
pub mod panic;
pub mod pybacked;
pub mod pycell;
pub mod pyclass;
pub mod pyclass_init;
pub mod type_object;
pub mod types;
mod version;
#[allow(
unused_imports,
reason = "with no features enabled this module has no public exports"
)]
pub use crate::conversions::*;
#[cfg(feature = "macros")]
pub use pyo3_macros::{
pyfunction, pymethods, pymodule, FromPyObject, IntoPyObject, IntoPyObjectRef,
};
/// A proc macro used to expose Rust structs and fieldless enums as Python objects.
///
#[doc = include_str!("../guide/pyclass-parameters.md")]
///
/// For more on creating Python classes,
/// see the [class section of the guide][1].
///
#[doc = concat!("[1]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html")]
#[cfg(feature = "macros")]
pub use pyo3_macros::pyclass;
#[cfg(feature = "macros")]
#[macro_use]
mod macros;
#[cfg(feature = "experimental-inspect")]
pub mod inspect;
// Putting the declaration of prelude at the end seems to help encourage rustc and rustdoc to prefer using
// other paths to the same items. (e.g. `pyo3::types::PyAnyMethods` instead of `pyo3::prelude::PyAnyMethods`).
pub mod prelude;
/// Test readme and user guide
#[cfg(doctest)]
pub mod doc_test {
macro_rules! doctests {
($($path:expr => $mod:ident),* $(,)?) => {
$(
#[doc = include_str!(concat!("../", $path))]
mod $mod{}
)*
};
}
doctests! {
"README.md" => readme_md,
"guide/src/advanced.md" => guide_advanced_md,
"guide/src/async-await.md" => guide_async_await_md,
"guide/src/building-and-distribution.md" => guide_building_and_distribution_md,
"guide/src/building-and-distribution/multiple-python-versions.md" => guide_bnd_multiple_python_versions_md,
"guide/src/class.md" => guide_class_md,
"guide/src/class/call.md" => guide_class_call,
"guide/src/class/object.md" => guide_class_object,
"guide/src/class/numeric.md" => guide_class_numeric,
"guide/src/class/protocols.md" => guide_class_protocols_md,
"guide/src/class/thread-safety.md" => guide_class_thread_safety_md,
"guide/src/conversions.md" => guide_conversions_md,
"guide/src/conversions/tables.md" => guide_conversions_tables_md,
"guide/src/conversions/traits.md" => guide_conversions_traits_md,
"guide/src/debugging.md" => guide_debugging_md,
// deliberate choice not to test guide/ecosystem because those pages depend on external
// crates such as pyo3_asyncio.
"guide/src/exception.md" => guide_exception_md,
"guide/src/faq.md" => guide_faq_md,
"guide/src/features.md" => guide_features_md,
"guide/src/free-threading.md" => guide_free_threading_md,
"guide/src/function.md" => guide_function_md,
"guide/src/function/error-handling.md" => guide_function_error_handling_md,
"guide/src/function/signature.md" => guide_function_signature_md,
"guide/src/migration.md" => guide_migration_md,
"guide/src/module.md" => guide_module_md,
"guide/src/parallelism.md" => guide_parallelism_md,
"guide/src/performance.md" => guide_performance_md,
"guide/src/python-from-rust.md" => guide_python_from_rust_md,
"guide/src/python-from-rust/calling-existing-code.md" => guide_pfr_calling_existing_code_md,
"guide/src/python-from-rust/function-calls.md" => guide_pfr_function_calls_md,
"guide/src/python-typing-hints.md" => guide_python_typing_hints_md,
"guide/src/rust-from-python.md" => guide_rust_from_python_md,
"guide/src/trait-bounds.md" => guide_trait_bounds_md,
"guide/src/types.md" => guide_types_md,
}
}
+230
View File
@@ -0,0 +1,230 @@
/// A convenient macro to execute a Python code snippet, with some local variables set.
///
/// # Panics
///
/// This macro internally calls [`Python::run`](crate::Python::run) and panics
/// if it returns `Err`, after printing the error to stdout.
///
/// If you need to handle failures, please use [`Python::run`](crate::marker::Python::run) instead.
///
/// # Examples
/// ```
/// use pyo3::{prelude::*, py_run, types::PyList};
///
/// # fn main() -> PyResult<()> {
/// Python::attach(|py| {
/// let list = PyList::new(py, &[1, 2, 3])?;
/// py_run!(py, list, "assert list == [1, 2, 3]");
/// # Ok(())
/// })
/// # }
/// ```
///
/// You can use this macro to test pyfunctions or pyclasses quickly.
///
/// ```
/// use pyo3::{prelude::*, py_run};
///
/// #[pyclass]
/// #[derive(Debug)]
/// struct Time {
/// hour: u32,
/// minute: u32,
/// second: u32,
/// }
///
/// #[pymethods]
/// impl Time {
/// fn repl_japanese(&self) -> String {
/// format!("{}時{}分{}秒", self.hour, self.minute, self.second)
/// }
/// #[getter]
/// fn hour(&self) -> u32 {
/// self.hour
/// }
/// fn as_tuple(&self) -> (u32, u32, u32) {
/// (self.hour, self.minute, self.second)
/// }
/// }
///
/// Python::attach(|py| {
/// let time = Py::new(py, Time {hour: 8, minute: 43, second: 16}).unwrap();
/// let time_as_tuple = (8, 43, 16);
/// py_run!(py, time time_as_tuple, r#"
/// assert time.hour == 8
/// assert time.repl_japanese() == "8時43分16秒"
/// assert time.as_tuple() == time_as_tuple
/// "#);
/// });
/// ```
///
/// If you need to prepare the `locals` dict by yourself, you can pass it as `*locals`.
///
/// ```
/// use pyo3::prelude::*;
/// use pyo3::types::IntoPyDict;
///
/// #[pyclass]
/// struct MyClass;
///
/// #[pymethods]
/// impl MyClass {
/// #[new]
/// fn new() -> Self {
/// MyClass {}
/// }
/// }
///
/// # fn main() -> PyResult<()> {
/// Python::attach(|py| {
/// let locals = [("C", py.get_type::<MyClass>())].into_py_dict(py)?;
/// pyo3::py_run!(py, *locals, "c = C()");
/// # Ok(())
/// })
/// # }
/// ```
#[macro_export]
macro_rules! py_run {
// unindent the code at compile time
($py:expr, $($val:ident)+, $code:literal) => {{
$crate::py_run_impl!($py, $($val)+, $crate::impl_::unindent::unindent!($code))
}};
($py:expr, *$dict:expr, $code:literal) => {{
$crate::py_run_impl!($py, *$dict, $crate::impl_::unindent::unindent!($code))
}};
// unindent the code at runtime
($py:expr, $($val:ident)+, $code:expr) => {{
$crate::py_run_impl!($py, $($val)+, $crate::impl_::unindent::unindent($code))
}};
($py:expr, *$dict:expr, $code:expr) => {{
$crate::py_run_impl!($py, *$dict, $crate::impl_::unindent::unindent($code))
}};
}
/// Internal implementation of the `py_run!` macro.
///
/// FIXME: this currently unconditionally allocates a `CString`. We should consider making this not so:
/// - Maybe require users to pass `&CStr` / `CString`?
/// - Maybe adjust the `unindent` code to produce `&Cstr` / `Cstring`?
#[macro_export]
#[doc(hidden)]
macro_rules! py_run_impl {
($py:expr, $($val:ident)+, $code:expr) => {{
use $crate::types::IntoPyDict;
use $crate::conversion::IntoPyObject;
use $crate::BoundObject;
let d = [$((stringify!($val), (&$val).into_pyobject($py).unwrap().into_any().into_bound()),)+].into_py_dict($py).unwrap();
$crate::py_run_impl!($py, *d, $code)
}};
($py:expr, *$dict:expr, $code:expr) => {{
use ::core::option::Option::*;
if let ::core::result::Result::Err(e) = $py.run(&::std::ffi::CString::new($code).unwrap(), None, Some(&$dict)) {
e.print($py);
// So when this c api function the last line called printed the error to stderr,
// the output is only written into a buffer which is never flushed because we
// panic before flushing. This is where this hack comes into place
$py.run(c"import sys; sys.stderr.flush()", None, None)
.unwrap();
::core::panic!("{}", $code)
}
}};
}
/// Wraps a Rust function annotated with [`#[pyfunction]`](macro@crate::pyfunction).
///
/// This can be used with [`PyModule::add_function`](crate::types::PyModuleMethods::add_function) to
/// add free functions to a [`PyModule`](crate::types::PyModule) - see its documentation for more
/// information.
///
/// # Examples
/// ```
/// use pyo3::prelude::*;
/// #[pyfunction]
/// fn add(x: i32, y: i32) -> i32 {
/// x + y
/// }
///
/// # fn main() -> PyResult<()> {
/// Python::attach(|py| {
/// let example = PyModule::from_code(
/// py,
/// c"from collections.abc import Callable
/// def add_two_and_three(add: 'Callable[[int, int], int]') -> int:
/// return add(2, 3)",
/// c"example.py",
/// c"",
/// )?;
///
/// // `add_two_and_three` is a Python function defined in the code above
/// let add_two_and_three = example.getattr("add_two_and_three")?;
///
/// // `add` is a Python function defined by the `#[pyfunction]` macro
/// let add = wrap_pyfunction!(add, py)?;
///
/// let result = add_two_and_three.call1((add,))?.extract::<i32>()?;
///
/// assert_eq!(result, 5);
///
/// # Ok(())
/// })
/// # }
/// ```
#[macro_export]
macro_rules! wrap_pyfunction {
($function:path) => {
&|py_or_module| {
use $function as wrapped_pyfunction;
$crate::impl_::pyfunction::WrapPyFunctionArg::wrap_pyfunction(
py_or_module,
&wrapped_pyfunction::_PYO3_DEF,
)
}
};
($function:path, $py_or_module:expr) => {{
use $function as wrapped_pyfunction;
$crate::impl_::pyfunction::WrapPyFunctionArg::wrap_pyfunction(
$py_or_module,
&wrapped_pyfunction::_PYO3_DEF,
)
}};
}
/// Returns a function that takes a [`Python`](crate::Python) instance and returns a
/// Python module.
///
/// Use this together with [`#[pymodule]`](crate::pymodule) and
/// [`PyModule::add_wrapped`](crate::types::PyModuleMethods::add_wrapped).
#[macro_export]
macro_rules! wrap_pymodule {
($module:path) => {
&|py| {
use $module as wrapped_pymodule;
wrapped_pymodule::_PYO3_DEF
.make_module(py)
.expect("failed to wrap pymodule")
}
};
}
/// Add the module to the initialization table in order to make embedded Python code to use it.
/// Module name is the argument.
///
/// Use it before [`Python::initialize`](crate::marker::Python::initialize) and
/// leave feature `auto-initialize` off
#[cfg(not(any(PyPy, GraalPy, all(Py_LIMITED_API, Py_GIL_DISABLED))))]
#[macro_export]
macro_rules! append_to_inittab {
($module:ident) => {
unsafe {
if $crate::ffi::Py_IsInitialized() != 0 {
::core::panic!(
"called `append_to_inittab` but a Python interpreter is already running."
);
}
$crate::ffi::PyImport_AppendInittab(
$module::__PYO3_NAME.as_ptr(),
::core::option::Option::Some($module::__pyo3_init),
);
}
};
}
+1037
View File
File diff suppressed because it is too large. Load diff
+76
View File
@@ -0,0 +1,76 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
#![cfg(not(Py_LIMITED_API))]
//! Support for the Python `marshal` format.
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::py_result_ext::PyResultExt;
use crate::types::{PyAny, PyBytes};
use crate::{ffi, Bound};
use crate::{PyResult, Python};
use core::ffi::c_int;
/// The current version of the marshal binary format.
pub const VERSION: i32 = 4;
/// Serialize an object to bytes using the Python built-in marshal module.
///
/// The built-in marshalling only supports a limited range of objects.
/// The exact types supported depend on the version argument.
/// The [`VERSION`] constant holds the highest version currently supported.
///
/// See the [Python documentation](https://docs.python.org/3/library/marshal.html) for more details.
///
/// # Examples
/// ```
/// # use pyo3::{marshal, types::PyDict, prelude::PyDictMethods};
/// # pyo3::Python::attach(|py| {
/// let dict = PyDict::new(py);
/// dict.set_item("aap", "noot").unwrap();
/// dict.set_item("mies", "wim").unwrap();
/// dict.set_item("zus", "jet").unwrap();
///
/// let bytes = marshal::dumps(&dict, marshal::VERSION);
/// # });
/// ```
pub fn dumps<'py>(object: &Bound<'py, PyAny>, version: i32) -> PyResult<Bound<'py, PyBytes>> {
unsafe {
ffi::PyMarshal_WriteObjectToString(object.as_ptr(), version as c_int)
.assume_owned_or_err(object.py())
.cast_into_unchecked()
}
}
/// Deserialize an object from bytes using the Python built-in marshal module.
pub fn loads<'py, B>(py: Python<'py>, data: &B) -> PyResult<Bound<'py, PyAny>>
where
B: AsRef<[u8]> + ?Sized,
{
let data = data.as_ref();
unsafe {
ffi::PyMarshal_ReadObjectFromString(data.as_ptr().cast(), data.len() as isize)
.assume_owned_or_err(py)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{bytes::PyBytesMethods, dict::PyDictMethods, PyAnyMethods, PyDict};
#[test]
fn marshal_roundtrip() {
Python::attach(|py| {
let dict = PyDict::new(py);
dict.set_item("aap", "noot").unwrap();
dict.set_item("mies", "wim").unwrap();
dict.set_item("zus", "jet").unwrap();
let pybytes = dumps(&dict, VERSION).expect("marshalling failed");
let deserialized = loads(py, pybytes.as_bytes()).expect("unmarshalling failed");
assert!(dict.eq(&deserialized).unwrap());
});
}
}
+32
View File
@@ -0,0 +1,32 @@
//! Helper to convert Rust panics to Python exceptions.
use crate::exceptions::PyBaseException;
use crate::PyErr;
use core::any::Any;
pyo3_exception!(
"
The exception raised when Rust code called from Python panics.
Like SystemExit, this exception is derived from BaseException so that
it will typically propagate all the way through the stack and cause the
Python interpreter to exit.
",
PanicException,
PyBaseException
);
impl PanicException {
/// Creates a new PanicException from a panic payload.
///
/// Attempts to format the error in the same way panic does.
#[cold]
pub(crate) fn from_panic_payload(payload: Box<dyn Any + Send + 'static>) -> PyErr {
if let Some(string) = payload.downcast_ref::<String>() {
Self::new_err((string.clone(),))
} else if let Some(s) = payload.downcast_ref::<&str>() {
Self::new_err((s.to_string(),))
} else {
Self::new_err(("panic from Rust code",))
}
}
}
+9
View File
@@ -0,0 +1,9 @@
//! This module is to support platform compatibility with `no_std` environments.
#![allow(unused_imports)]
#[cfg(feature = "hashbrown")]
pub use hashbrown::{HashMap, HashSet};
// TODO conditionally import these based on "std" feature
#[cfg(not(feature = "hashbrown"))]
pub use std::collections::{HashMap, HashSet};
+48
View File
@@ -0,0 +1,48 @@
//! PyO3's prelude.
//!
//! The purpose of this module is to alleviate imports of many commonly used items of the PyO3 crate
//! by adding a glob import to the top of pyo3 heavy modules:
//!
//! ```
//! # #![allow(unused_imports)]
//! use pyo3::prelude::*;
//! ```
pub use crate::conversion::{FromPyObject, FromPyObjectOwned, IntoPyObject};
pub use crate::err::{PyErr, PyResult};
pub use crate::instance::{Borrowed, Bound, Py};
pub use crate::marker::Python;
pub use crate::pycell::{PyRef, PyRefMut};
pub use crate::pyclass_init::PyClassInitializer;
pub use crate::types::{PyAny, PyModule};
pub use crate::{PyClassGuard, PyClassGuardMut};
#[cfg(feature = "macros")]
pub use pyo3_macros::{
pyclass, pyfunction, pymethods, pymodule, FromPyObject, IntoPyObject, IntoPyObjectRef,
};
#[cfg(feature = "macros")]
pub use crate::wrap_pyfunction;
pub use crate::types::any::PyAnyMethods;
pub use crate::types::boolobject::PyBoolMethods;
pub use crate::types::bytearray::PyByteArrayMethods;
pub use crate::types::bytes::PyBytesMethods;
pub use crate::types::capsule::PyCapsuleMethods;
pub use crate::types::complex::PyComplexMethods;
pub use crate::types::dict::PyDictMethods;
pub use crate::types::float::PyFloatMethods;
pub use crate::types::frozenset::PyFrozenSetMethods;
pub use crate::types::list::PyListMethods;
pub use crate::types::mapping::PyMappingMethods;
pub use crate::types::mappingproxy::PyMappingProxyMethods;
pub use crate::types::module::PyModuleMethods;
pub use crate::types::sequence::PySequenceMethods;
pub use crate::types::set::PySetMethods;
pub use crate::types::slice::PySliceMethods;
pub use crate::types::string::PyStringMethods;
pub use crate::types::traceback::PyTracebackMethods;
pub use crate::types::tuple::PyTupleMethods;
pub use crate::types::typeobject::PyTypeMethods;
pub use crate::types::weakref::PyWeakrefMethods;
+21
View File
@@ -0,0 +1,21 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
use crate::{Bound, PyAny, PyResult, PyTypeCheck};
pub(crate) trait PyResultExt<'py>: crate::sealed::Sealed {
fn cast_into<T: PyTypeCheck>(self) -> PyResult<Bound<'py, T>>;
unsafe fn cast_into_unchecked<T>(self) -> PyResult<Bound<'py, T>>;
}
impl<'py> PyResultExt<'py> for PyResult<Bound<'py, PyAny>> {
#[inline]
fn cast_into<T: PyTypeCheck>(self) -> PyResult<Bound<'py, T>> where {
self.and_then(|instance| instance.cast_into().map_err(Into::into))
}
#[inline]
unsafe fn cast_into_unchecked<T>(self) -> PyResult<Bound<'py, T>> {
self.map(|instance| unsafe { instance.cast_into_unchecked() })
}
}
+776
View File
@@ -0,0 +1,776 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
//! Contains types for working with Python objects that own the underlying data.
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
#[cfg(feature = "experimental-inspect")]
use crate::type_hint_union;
use crate::{
types::{
bytearray::PyByteArrayMethods, bytes::PyBytesMethods, string::PyStringMethods, PyByteArray,
PyBytes, PyString, PyTuple,
},
Borrowed, Bound, CastError, FromPyObject, IntoPyObject, Py, PyAny, PyErr, PyTypeInfo, Python,
};
use alloc::sync::Arc;
use core::{borrow::Borrow, convert::Infallible, ops::Deref, ptr::NonNull};
/// An equivalent to `String` where the storage is owned by a Python `bytes` or `str` object.
///
/// On Python 3.10+ or when not using the stable API, this type is guaranteed to contain a Python `str`
/// for the underlying data.
///
/// This type gives access to the underlying data via a `Deref` implementation.
#[cfg_attr(feature = "py-clone", derive(Clone))]
pub struct PyBackedStr {
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
storage: Py<PyString>,
#[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
storage: Py<PyBytes>,
data: NonNull<str>,
}
impl PyBackedStr {
/// Clones this by incrementing the reference count of the underlying Python object.
///
/// Similar to [`Py::clone_ref`], this method is always available, even when the `py-clone` feature is disabled.
#[inline]
pub fn clone_ref(&self, py: Python<'_>) -> Self {
Self {
storage: self.storage.clone_ref(py),
data: self.data,
}
}
/// Returns the underlying data as a `&str` slice.
#[inline]
pub fn as_str(&self) -> &str {
// Safety: `data` is known to be immutable and owned by self
unsafe { self.data.as_ref() }
}
/// Returns the underlying data as a Python `str`.
///
/// Older versions of the Python stable API do not support this zero-cost conversion.
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
#[inline]
pub fn as_py_str(&self) -> &Py<PyString> {
&self.storage
}
}
impl Deref for PyBackedStr {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.as_str()
}
}
impl AsRef<str> for PyBackedStr {
#[inline]
fn as_ref(&self) -> &str {
self
}
}
impl AsRef<[u8]> for PyBackedStr {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl Borrow<str> for PyBackedStr {
#[inline]
fn borrow(&self) -> &str {
self
}
}
// Safety: the underlying Python str (or bytes) is immutable and
// safe to share between threads
unsafe impl Send for PyBackedStr {}
unsafe impl Sync for PyBackedStr {}
impl core::fmt::Display for PyBackedStr {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.deref().fmt(f)
}
}
impl_traits!(PyBackedStr, str);
impl TryFrom<Bound<'_, PyString>> for PyBackedStr {
type Error = PyErr;
fn try_from(py_string: Bound<'_, PyString>) -> Result<Self, Self::Error> {
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
{
let s = py_string.to_str()?;
let data = NonNull::from(s);
Ok(Self {
storage: py_string.unbind(),
data,
})
}
#[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
{
let bytes = py_string.encode_utf8()?;
let s = unsafe { core::str::from_utf8_unchecked(bytes.as_bytes()) };
let data = NonNull::from(s);
Ok(Self {
storage: bytes.unbind(),
data,
})
}
}
}
impl FromPyObject<'_, '_> for PyBackedStr {
type Error = PyErr;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
#[inline]
fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
let py_string = obj.cast::<PyString>()?.to_owned();
Self::try_from(py_string)
}
}
impl<'py> IntoPyObject<'py> for PyBackedStr {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.storage.into_bound(py))
}
#[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(PyString::new(py, &self))
}
}
impl<'py> IntoPyObject<'py> for &PyBackedStr {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = PyString::TYPE_HINT;
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.storage.bind(py).to_owned())
}
#[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(PyString::new(py, self))
}
}
/// A wrapper around `[u8]` where the storage is either owned by a Python `bytes` object, or a Rust `Box<[u8]>`.
///
/// This type gives access to the underlying data via a `Deref` implementation.
#[cfg_attr(feature = "py-clone", derive(Clone))]
pub struct PyBackedBytes {
storage: PyBackedBytesStorage,
data: NonNull<[u8]>,
}
#[cfg_attr(feature = "py-clone", derive(Clone))]
enum PyBackedBytesStorage {
Python(Py<PyBytes>),
Rust(Arc<[u8]>),
}
impl PyBackedBytes {
/// Clones this by incrementing the reference count of the underlying data.
///
/// Similar to [`Py::clone_ref`], this method is always available, even when the `py-clone` feature is disabled.
pub fn clone_ref(&self, py: Python<'_>) -> Self {
Self {
storage: match &self.storage {
PyBackedBytesStorage::Python(bytes) => {
PyBackedBytesStorage::Python(bytes.clone_ref(py))
}
PyBackedBytesStorage::Rust(bytes) => PyBackedBytesStorage::Rust(bytes.clone()),
},
data: self.data,
}
}
}
impl Deref for PyBackedBytes {
type Target = [u8];
fn deref(&self) -> &[u8] {
// Safety: `data` is known to be immutable and owned by self
unsafe { self.data.as_ref() }
}
}
impl AsRef<[u8]> for PyBackedBytes {
fn as_ref(&self) -> &[u8] {
self
}
}
// Safety: the underlying Python bytes or Rust bytes is immutable and
// safe to share between threads
unsafe impl Send for PyBackedBytes {}
unsafe impl Sync for PyBackedBytes {}
impl<const N: usize> PartialEq<[u8; N]> for PyBackedBytes {
fn eq(&self, other: &[u8; N]) -> bool {
self.deref() == other
}
}
impl<const N: usize> PartialEq<PyBackedBytes> for [u8; N] {
fn eq(&self, other: &PyBackedBytes) -> bool {
self == other.deref()
}
}
impl<const N: usize> PartialEq<&[u8; N]> for PyBackedBytes {
fn eq(&self, other: &&[u8; N]) -> bool {
self.deref() == *other
}
}
impl<const N: usize> PartialEq<PyBackedBytes> for &[u8; N] {
fn eq(&self, other: &PyBackedBytes) -> bool {
self == &other.deref()
}
}
impl_traits!(PyBackedBytes, [u8]);
impl From<Bound<'_, PyBytes>> for PyBackedBytes {
fn from(py_bytes: Bound<'_, PyBytes>) -> Self {
let b = py_bytes.as_bytes();
let data = NonNull::from(b);
Self {
storage: PyBackedBytesStorage::Python(py_bytes.to_owned().unbind()),
data,
}
}
}
impl From<Bound<'_, PyByteArray>> for PyBackedBytes {
fn from(py_bytearray: Bound<'_, PyByteArray>) -> Self {
let s = Arc::<[u8]>::from(py_bytearray.to_vec());
let data = NonNull::from(s.as_ref());
Self {
storage: PyBackedBytesStorage::Rust(s),
data,
}
}
}
impl<'a, 'py> FromPyObject<'a, 'py> for PyBackedBytes {
type Error = CastError<'a, 'py>;
#[cfg(feature = "experimental-inspect")]
const INPUT_TYPE: PyStaticExpr = type_hint_union!(PyBytes::TYPE_HINT, PyByteArray::TYPE_HINT);
fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
if let Ok(bytes) = obj.cast::<PyBytes>() {
Ok(Self::from(bytes.to_owned()))
} else if let Ok(bytearray) = obj.cast::<PyByteArray>() {
Ok(Self::from(bytearray.to_owned()))
} else {
Err(CastError::new(
obj,
PyTuple::new(
obj.py(),
[
PyBytes::type_object(obj.py()),
PyByteArray::type_object(obj.py()),
],
)
.unwrap()
.into_any(),
))
}
}
}
impl<'py> IntoPyObject<'py> for PyBackedBytes {
type Target = PyBytes;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = PyBytes::TYPE_HINT;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
match self.storage {
PyBackedBytesStorage::Python(bytes) => Ok(bytes.into_bound(py)),
PyBackedBytesStorage::Rust(bytes) => Ok(PyBytes::new(py, &bytes)),
}
}
}
impl<'py> IntoPyObject<'py> for &PyBackedBytes {
type Target = PyBytes;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = PyBytes::TYPE_HINT;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
match &self.storage {
PyBackedBytesStorage::Python(bytes) => Ok(bytes.bind(py).clone()),
PyBackedBytesStorage::Rust(bytes) => Ok(PyBytes::new(py, bytes)),
}
}
}
macro_rules! impl_traits {
($slf:ty, $equiv:ty) => {
impl core::fmt::Debug for $slf {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.deref().fmt(f)
}
}
impl PartialEq for $slf {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.deref() == other.deref()
}
}
impl PartialEq<$equiv> for $slf {
#[inline]
fn eq(&self, other: &$equiv) -> bool {
self.deref() == other
}
}
impl PartialEq<&$equiv> for $slf {
#[inline]
fn eq(&self, other: &&$equiv) -> bool {
self.deref() == *other
}
}
impl PartialEq<$slf> for $equiv {
#[inline]
fn eq(&self, other: &$slf) -> bool {
self == other.deref()
}
}
impl PartialEq<$slf> for &$equiv {
#[inline]
fn eq(&self, other: &$slf) -> bool {
self == &other.deref()
}
}
impl Eq for $slf {}
impl PartialOrd for $slf {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl PartialOrd<$equiv> for $slf {
#[inline]
fn partial_cmp(&self, other: &$equiv) -> Option<core::cmp::Ordering> {
self.deref().partial_cmp(other)
}
}
impl PartialOrd<$slf> for $equiv {
#[inline]
fn partial_cmp(&self, other: &$slf) -> Option<core::cmp::Ordering> {
self.partial_cmp(other.deref())
}
}
impl Ord for $slf {
#[inline]
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.deref().cmp(other.deref())
}
}
impl core::hash::Hash for $slf {
#[inline]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.deref().hash(state)
}
}
};
}
use impl_traits;
#[cfg(test)]
mod test {
use super::*;
use crate::impl_::pyclass::{value_of, IsSend, IsSync};
use crate::types::PyAnyMethods as _;
use crate::{IntoPyObject, Python};
use core::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher;
#[test]
fn py_backed_str_empty() {
Python::attach(|py| {
let s = PyString::new(py, "");
let py_backed_str = s.extract::<PyBackedStr>().unwrap();
assert_eq!(&*py_backed_str, "");
});
}
#[test]
fn py_backed_str() {
Python::attach(|py| {
let s = PyString::new(py, "hello");
let py_backed_str = s.extract::<PyBackedStr>().unwrap();
assert_eq!(&*py_backed_str, "hello");
});
}
#[test]
fn py_backed_str_try_from() {
Python::attach(|py| {
let s = PyString::new(py, "hello");
let py_backed_str = PyBackedStr::try_from(s).unwrap();
assert_eq!(&*py_backed_str, "hello");
});
}
#[test]
fn py_backed_str_into_pyobject() {
Python::attach(|py| {
let orig_str = PyString::new(py, "hello");
let py_backed_str = orig_str.extract::<PyBackedStr>().unwrap();
let new_str = py_backed_str.into_pyobject(py).unwrap();
assert_eq!(new_str.extract::<PyBackedStr>().unwrap(), "hello");
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
assert!(new_str.is(&orig_str));
});
}
#[test]
fn py_backed_bytes_empty() {
Python::attach(|py| {
let b = PyBytes::new(py, b"");
let py_backed_bytes = b.extract::<PyBackedBytes>().unwrap();
assert_eq!(&*py_backed_bytes, b"");
});
}
#[test]
fn py_backed_bytes() {
Python::attach(|py| {
let b = PyBytes::new(py, b"abcde");
let py_backed_bytes = b.extract::<PyBackedBytes>().unwrap();
assert_eq!(&*py_backed_bytes, b"abcde");
});
}
#[test]
fn py_backed_bytes_from_bytes() {
Python::attach(|py| {
let b = PyBytes::new(py, b"abcde");
let py_backed_bytes = PyBackedBytes::from(b);
assert_eq!(&*py_backed_bytes, b"abcde");
});
}
#[test]
fn py_backed_bytes_from_bytearray() {
Python::attach(|py| {
let b = PyByteArray::new(py, b"abcde");
let py_backed_bytes = PyBackedBytes::from(b);
assert_eq!(&*py_backed_bytes, b"abcde");
});
}
#[test]
fn py_backed_bytes_into_pyobject() {
Python::attach(|py| {
let orig_bytes = PyBytes::new(py, b"abcde");
let py_backed_bytes = PyBackedBytes::from(orig_bytes.clone());
assert!((&py_backed_bytes)
.into_pyobject(py)
.unwrap()
.is(&orig_bytes));
});
}
#[test]
fn rust_backed_bytes_into_pyobject() {
Python::attach(|py| {
let orig_bytes = PyByteArray::new(py, b"abcde");
let rust_backed_bytes = PyBackedBytes::from(orig_bytes);
assert!(matches!(
rust_backed_bytes.storage,
PyBackedBytesStorage::Rust(_)
));
let to_object = (&rust_backed_bytes).into_pyobject(py).unwrap();
assert!(&to_object.is_exact_instance_of::<PyBytes>());
assert_eq!(&to_object.extract::<PyBackedBytes>().unwrap(), b"abcde");
});
}
#[test]
fn test_backed_types_send_sync() {
assert!(value_of!(IsSend, PyBackedStr));
assert!(value_of!(IsSync, PyBackedStr));
assert!(value_of!(IsSend, PyBackedBytes));
assert!(value_of!(IsSync, PyBackedBytes));
}
#[cfg(feature = "py-clone")]
#[test]
fn test_backed_str_clone() {
Python::attach(|py| {
let s1: PyBackedStr = PyString::new(py, "hello").try_into().unwrap();
let s2 = s1.clone();
assert_eq!(s1, s2);
drop(s1);
assert_eq!(s2, "hello");
});
}
#[test]
fn test_backed_str_clone_ref() {
Python::attach(|py| {
let s1: PyBackedStr = PyString::new(py, "hello").try_into().unwrap();
let s2 = s1.clone_ref(py);
assert_eq!(s1, s2);
assert!(s1.storage.is(&s2.storage));
drop(s1);
assert_eq!(s2, "hello");
});
}
#[test]
fn test_backed_str_eq() {
Python::attach(|py| {
let s1: PyBackedStr = PyString::new(py, "hello").try_into().unwrap();
let s2: PyBackedStr = PyString::new(py, "hello").try_into().unwrap();
assert_eq!(s1, "hello");
assert_eq!(s1, s2);
let s3: PyBackedStr = PyString::new(py, "abcde").try_into().unwrap();
assert_eq!("abcde", s3);
assert_ne!(s1, s3);
});
}
#[test]
fn test_backed_str_hash() {
Python::attach(|py| {
let h = {
let mut hasher = DefaultHasher::new();
"abcde".hash(&mut hasher);
hasher.finish()
};
let s1: PyBackedStr = PyString::new(py, "abcde").try_into().unwrap();
let h1 = {
let mut hasher = DefaultHasher::new();
s1.hash(&mut hasher);
hasher.finish()
};
assert_eq!(h, h1);
});
}
#[test]
fn test_backed_str_ord() {
Python::attach(|py| {
let mut a = vec!["a", "c", "d", "b", "f", "g", "e"];
let mut b = a
.iter()
.map(|s| PyString::new(py, s).try_into().unwrap())
.collect::<Vec<PyBackedStr>>();
a.sort();
b.sort();
assert_eq!(a, b);
})
}
#[test]
fn test_backed_str_map_key() {
Python::attach(|py| {
use crate::platform::HashMap;
let mut map: HashMap<PyBackedStr, usize> = HashMap::new();
let s: PyBackedStr = PyString::new(py, "key1").try_into().unwrap();
map.insert(s, 1);
assert_eq!(map.get("key1"), Some(&1));
});
}
#[test]
fn test_backed_str_as_str() {
Python::attach(|py| {
let s: PyBackedStr = PyString::new(py, "hello").try_into().unwrap();
assert_eq!(s.as_str(), "hello");
});
}
#[test]
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
fn test_backed_str_as_py_str() {
Python::attach(|py| {
let s: PyBackedStr = PyString::new(py, "hello").try_into().unwrap();
let py_str = s.as_py_str().bind(py);
assert!(py_str.is(&s.storage));
assert_eq!(py_str.to_str().unwrap(), "hello");
});
}
#[cfg(feature = "py-clone")]
#[test]
fn test_backed_bytes_from_bytes_clone() {
Python::attach(|py| {
let b1: PyBackedBytes = PyBytes::new(py, b"abcde").into();
let b2 = b1.clone();
assert_eq!(b1, b2);
drop(b1);
assert_eq!(b2, b"abcde");
});
}
#[test]
fn test_backed_bytes_from_bytes_clone_ref() {
Python::attach(|py| {
let b1: PyBackedBytes = PyBytes::new(py, b"abcde").into();
let b2 = b1.clone_ref(py);
assert_eq!(b1, b2);
let (PyBackedBytesStorage::Python(s1), PyBackedBytesStorage::Python(s2)) =
(&b1.storage, &b2.storage)
else {
panic!("Expected Python-backed bytes");
};
assert!(s1.is(s2));
drop(b1);
assert_eq!(b2, b"abcde");
});
}
#[cfg(feature = "py-clone")]
#[test]
fn test_backed_bytes_from_bytearray_clone() {
Python::attach(|py| {
let b1: PyBackedBytes = PyByteArray::new(py, b"abcde").into();
let b2 = b1.clone();
assert_eq!(b1, b2);
drop(b1);
assert_eq!(b2, b"abcde");
});
}
#[test]
fn test_backed_bytes_from_bytearray_clone_ref() {
Python::attach(|py| {
let b1: PyBackedBytes = PyByteArray::new(py, b"abcde").into();
let b2 = b1.clone_ref(py);
assert_eq!(b1, b2);
let (PyBackedBytesStorage::Rust(s1), PyBackedBytesStorage::Rust(s2)) =
(&b1.storage, &b2.storage)
else {
panic!("Expected Rust-backed bytes");
};
assert!(Arc::ptr_eq(s1, s2));
drop(b1);
assert_eq!(b2, b"abcde");
});
}
#[test]
fn test_backed_bytes_eq() {
Python::attach(|py| {
let b1: PyBackedBytes = PyBytes::new(py, b"abcde").into();
let b2: PyBackedBytes = PyByteArray::new(py, b"abcde").into();
assert_eq!(b1, b"abcde");
assert_eq!(b1, b2);
let b3: PyBackedBytes = PyBytes::new(py, b"hello").into();
assert_eq!(b"hello", b3);
assert_ne!(b1, b3);
});
}
#[test]
fn test_backed_bytes_hash() {
Python::attach(|py| {
let h = {
let mut hasher = DefaultHasher::new();
b"abcde".hash(&mut hasher);
hasher.finish()
};
let b1: PyBackedBytes = PyBytes::new(py, b"abcde").into();
let h1 = {
let mut hasher = DefaultHasher::new();
b1.hash(&mut hasher);
hasher.finish()
};
let b2: PyBackedBytes = PyByteArray::new(py, b"abcde").into();
let h2 = {
let mut hasher = DefaultHasher::new();
b2.hash(&mut hasher);
hasher.finish()
};
assert_eq!(h, h1);
assert_eq!(h, h2);
});
}
#[test]
fn test_backed_bytes_ord() {
Python::attach(|py| {
let mut a = vec![b"a", b"c", b"d", b"b", b"f", b"g", b"e"];
let mut b = a
.iter()
.map(|&b| PyBytes::new(py, b).into())
.collect::<Vec<PyBackedBytes>>();
a.sort();
b.sort();
assert_eq!(a, b);
})
}
}
+952
View File
@@ -0,0 +1,952 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
//! PyO3's interior mutability primitive.
//!
//! Rust has strict aliasing rules - you can either have any number of immutable (shared) references or one mutable
//! reference. Python's ownership model is the complete opposite of that - any Python object
//! can be referenced any number of times, and mutation is allowed from any reference.
//!
//! PyO3 deals with these differences by employing the [Interior Mutability]
//! pattern. This requires that PyO3 enforces the borrowing rules and it has two mechanisms for
//! doing so:
//! - Statically it can enforce thread-safe access with the [`Python<'py>`](crate::Python) token.
//! All Rust code holding that token, or anything derived from it, can assume that they have
//! safe access to the Python interpreter's state. For this reason all the native Python objects
//! can be mutated through shared references.
//! - However, methods and functions in Rust usually *do* need `&mut` references. While PyO3 can
//! use the [`Python<'py>`](crate::Python) token to guarantee thread-safe access to them, it cannot
//! statically guarantee uniqueness of `&mut` references. As such those references have to be tracked
//! dynamically at runtime, using `PyCell` and the other types defined in this module. This works
//! similar to std's [`RefCell`](core::cell::RefCell) type.
//!
//! # When *not* to use PyCell
//!
//! Usually you can use `&mut` references as method and function receivers and arguments, and you
//! won't need to use `PyCell` directly:
//!
//! ```rust,no_run
//! use pyo3::prelude::*;
//!
//! #[pyclass]
//! struct Number {
//! inner: u32,
//! }
//!
//! #[pymethods]
//! impl Number {
//! fn increment(&mut self) {
//! self.inner += 1;
//! }
//! }
//! ```
//!
//! The [`#[pymethods]`](crate::pymethods) proc macro will generate this wrapper function (and more),
//! using `PyCell` under the hood:
//!
//! ```rust,ignore
//! # use pyo3::prelude::*;
//! # #[pyclass]
//! # struct Number {
//! # inner: u32,
//! # }
//! #
//! # #[pymethods]
//! # impl Number {
//! # fn increment(&mut self) {
//! # self.inner += 1;
//! # }
//! # }
//! #
//! // The function which is exported to Python looks roughly like the following
//! unsafe extern "C" fn __pymethod_increment__(
//! _slf: *mut pyo3::ffi::PyObject,
//! _args: *mut pyo3::ffi::PyObject,
//! ) -> *mut pyo3::ffi::PyObject {
//! use :: pyo3 as _pyo3;
//! _pyo3::impl_::trampoline::noargs(_slf, _args, |py, _slf| {
//! # #[allow(deprecated)]
//! let _cell = py
//! .from_borrowed_ptr::<_pyo3::PyAny>(_slf)
//! .cast::<_pyo3::PyCell<Number>>()?;
//! let mut _ref = _cell.try_borrow_mut()?;
//! let _slf: &mut Number = &mut *_ref;
//! _pyo3::impl_::callback::convert(py, Number::increment(_slf))
//! })
//! }
//! ```
//!
//! # When to use PyCell
//! ## Using pyclasses from Rust
//!
//! However, we *do* need `PyCell` if we want to call its methods from Rust:
//! ```rust
//! # use pyo3::prelude::*;
//! #
//! # #[pyclass]
//! # struct Number {
//! # inner: u32,
//! # }
//! #
//! # #[pymethods]
//! # impl Number {
//! # fn increment(&mut self) {
//! # self.inner += 1;
//! # }
//! # }
//! # fn main() -> PyResult<()> {
//! Python::attach(|py| {
//! let n = Py::new(py, Number { inner: 0 })?;
//!
//! // We borrow the guard and then dereference
//! // it to get a mutable reference to Number
//! let mut guard: PyRefMut<'_, Number> = n.bind(py).borrow_mut();
//! let n_mutable: &mut Number = &mut *guard;
//!
//! n_mutable.increment();
//!
//! // To avoid panics we must dispose of the
//! // `PyRefMut` before borrowing again.
//! drop(guard);
//!
//! let n_immutable: &Number = &n.bind(py).borrow();
//! assert_eq!(n_immutable.inner, 1);
//!
//! Ok(())
//! })
//! # }
//! ```
//! ## Dealing with possibly overlapping mutable references
//!
//! It is also necessary to use `PyCell` if you can receive mutable arguments that may overlap.
//! Suppose the following function that swaps the values of two `Number`s:
//! ```
//! # use pyo3::prelude::*;
//! # #[pyclass]
//! # pub struct Number {
//! # inner: u32,
//! # }
//! #[pyfunction]
//! fn swap_numbers(a: &mut Number, b: &mut Number) {
//! core::mem::swap(&mut a.inner, &mut b.inner);
//! }
//! # fn main() {
//! # Python::attach(|py| {
//! # let n = Py::new(py, Number{inner: 35}).unwrap();
//! # let n2 = n.clone_ref(py);
//! # assert!(n.is(&n2));
//! # let fun = pyo3::wrap_pyfunction!(swap_numbers, py).unwrap();
//! # fun.call1((n, n2)).expect_err("Managed to create overlapping mutable references. Note: this is undefined behaviour.");
//! # });
//! # }
//! ```
//! When users pass in the same `Number` as both arguments, one of the mutable borrows will
//! fail and raise a `RuntimeError`:
//! ```text
//! >>> a = Number()
//! >>> swap_numbers(a, a)
//! Traceback (most recent call last):
//! File "<stdin>", line 1, in <module>
//! RuntimeError: Already borrowed
//! ```
//!
//! It is better to write that function like this:
//! ```rust,ignore
//! # #![allow(deprecated)]
//! # use pyo3::prelude::*;
//! # #[pyclass]
//! # pub struct Number {
//! # inner: u32,
//! # }
//! #[pyfunction]
//! fn swap_numbers(a: &PyCell<Number>, b: &PyCell<Number>) {
//! // Check that the pointers are unequal
//! if !a.is(b) {
//! core::mem::swap(&mut a.borrow_mut().inner, &mut b.borrow_mut().inner);
//! } else {
//! // Do nothing - they are the same object, so don't need swapping.
//! }
//! }
//! # fn main() {
//! # // With duplicate numbers
//! # Python::attach(|py| {
//! # let n = Py::new(py, Number{inner: 35}).unwrap();
//! # let n2 = n.clone_ref(py);
//! # assert!(n.is(&n2));
//! # let fun = pyo3::wrap_pyfunction!(swap_numbers, py).unwrap();
//! # fun.call1((n, n2)).unwrap();
//! # });
//! #
//! # // With two different numbers
//! # Python::attach(|py| {
//! # let n = Py::new(py, Number{inner: 35}).unwrap();
//! # let n2 = Py::new(py, Number{inner: 42}).unwrap();
//! # assert!(!n.is(&n2));
//! # let fun = pyo3::wrap_pyfunction!(swap_numbers, py).unwrap();
//! # fun.call1((&n, &n2)).unwrap();
//! # let n: u32 = n.borrow(py).inner;
//! # let n2: u32 = n2.borrow(py).inner;
//! # assert_eq!(n, 42);
//! # assert_eq!(n2, 35);
//! # });
//! # }
//! ```
//! See the [guide] for more information.
//!
//! [guide]: https://pyo3.rs/latest/class.html#pycell-and-interior-mutability "PyCell and interior mutability"
//! [Interior Mutability]: https://doc.rust-lang.org/book/ch15-05-interior-mutability.html "RefCell<T> and the Interior Mutability Pattern - The Rust Programming Language"
use crate::conversion::IntoPyObject;
use crate::exceptions::PyRuntimeError;
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::pyclass::{boolean_struct::False, PyClass};
use crate::{ffi, Borrowed, Bound, PyErr, Python};
use core::convert::Infallible;
use core::fmt;
use core::mem::ManuallyDrop;
use core::ops::{Deref, DerefMut};
use core::ptr::NonNull;
pub(crate) mod impl_;
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
use impl_::{PyClassBorrowChecker, PyClassObjectBaseLayout, PyClassObjectLayout};
/// A wrapper type for an immutably borrowed value from a [`Bound<'py, T>`].
///
/// See the [`Bound`] documentation for more information.
///
/// # Examples
///
/// You can use [`PyRef`] as an alternative to a `&self` receiver when
/// - you need to access the pointer of the [`Bound`], or
/// - you want to get a super class.
/// ```
/// # use pyo3::prelude::*;
/// #[pyclass(subclass)]
/// struct Parent {
/// basename: &'static str,
/// }
///
/// #[pyclass(extends=Parent)]
/// struct Child {
/// name: &'static str,
/// }
///
/// #[pymethods]
/// impl Child {
/// #[new]
/// fn new() -> PyClassInitializer<Self> {
/// PyClassInitializer::from(Parent { basename: "Butterfly" })
/// .add_subclass(Child { name: "Caterpillar" })
/// }
///
/// fn format(slf: PyRef<'_, Self>) -> String {
/// // We can get *mut ffi::PyObject from PyRef
/// let refcnt = unsafe { pyo3::ffi::Py_REFCNT(slf.as_ptr()) };
/// // We can get &Self::BaseType by as_ref
/// let basename = slf.as_ref().basename;
/// format!("{}(base: {}, cnt: {})", slf.name, basename, refcnt)
/// }
/// }
/// # Python::attach(|py| {
/// # let sub = Py::new(py, Child::new()).unwrap();
/// # pyo3::py_run!(py, sub, "assert sub.format() == 'Caterpillar(base: Butterfly, cnt: 4)', sub.format()");
/// # });
/// ```
///
/// See the [module-level documentation](self) for more information.
#[repr(transparent)]
pub struct PyRef<'p, T: PyClass> {
// TODO: once the GIL Ref API is removed, consider adding a lifetime parameter to `PyRef` to
// store `Borrowed` here instead, avoiding reference counting overhead.
inner: Bound<'p, T>,
}
impl<'p, T: PyClass> PyRef<'p, T> {
/// Returns a `Python` token that is bound to the lifetime of the `PyRef`.
pub fn py(&self) -> Python<'p> {
self.inner.py()
}
}
impl<T, U> AsRef<U> for PyRef<'_, T>
where
T: PyClass<BaseType = U>,
U: PyClass,
{
fn as_ref(&self) -> &T::BaseType {
self.as_super()
}
}
impl<'py, T: PyClass> PyRef<'py, T> {
/// Returns the raw FFI pointer represented by self.
///
/// # Safety
///
/// Callers are responsible for ensuring that the pointer does not outlive self.
///
/// The reference is borrowed; callers should not decrease the reference count
/// when they are finished with the pointer.
#[inline]
pub fn as_ptr(&self) -> *mut ffi::PyObject {
self.inner.as_ptr()
}
/// Returns an owned raw FFI pointer represented by self.
///
/// # Safety
///
/// The reference is owned; when finished the caller should either transfer ownership
/// of the pointer or decrease the reference count (e.g. with [`pyo3::ffi::Py_DecRef`](crate::ffi::Py_DecRef)).
#[inline]
pub fn into_ptr(self) -> *mut ffi::PyObject {
self.inner.clone().into_ptr()
}
#[track_caller]
pub(crate) fn borrow(obj: &Bound<'py, T>) -> Self {
Self::try_borrow(obj).expect("Already mutably borrowed")
}
pub(crate) fn try_borrow(obj: &Bound<'py, T>) -> Result<Self, PyBorrowError> {
let cell = obj.get_class_object();
cell.ensure_threadsafe();
cell.borrow_checker()
.try_borrow()
.map(|_| Self { inner: obj.clone() })
}
}
impl<'p, T> PyRef<'p, T>
where
T: PyClass,
T::BaseType: PyClass,
{
/// Gets a `PyRef<T::BaseType>`.
///
/// While `as_ref()` returns a reference of type `&T::BaseType`, this cannot be
/// used to get the base of `T::BaseType`.
///
/// But with the help of this method, you can get hold of instances of the
/// super-superclass when needed.
///
/// # Examples
/// ```
/// # use pyo3::prelude::*;
/// #[pyclass(subclass)]
/// struct Base1 {
/// name1: &'static str,
/// }
///
/// #[pyclass(extends=Base1, subclass)]
/// struct Base2 {
/// name2: &'static str,
/// }
///
/// #[pyclass(extends=Base2)]
/// struct Sub {
/// name3: &'static str,
/// }
///
/// #[pymethods]
/// impl Sub {
/// #[new]
/// fn new() -> PyClassInitializer<Self> {
/// PyClassInitializer::from(Base1 { name1: "base1" })
/// .add_subclass(Base2 { name2: "base2" })
/// .add_subclass(Self { name3: "sub" })
/// }
/// fn name(slf: PyRef<'_, Self>) -> String {
/// let subname = slf.name3;
/// let super_ = slf.into_super();
/// format!("{} {} {}", super_.as_ref().name1, super_.name2, subname)
/// }
/// }
/// # Python::attach(|py| {
/// # let sub = Py::new(py, Sub::new()).unwrap();
/// # pyo3::py_run!(py, sub, "assert sub.name() == 'base1 base2 sub'")
/// # });
/// ```
pub fn into_super(self) -> PyRef<'p, T::BaseType> {
let py = self.py();
let t_not_frozen = !<T::Frozen as crate::pyclass::boolean_struct::private::Boolean>::VALUE;
let u_frozen = <<T::BaseType as PyClass>::Frozen as crate::pyclass::boolean_struct::private::Boolean>::VALUE;
if t_not_frozen && u_frozen {
// If `T` is mutable subclass of `U` differ, then it is possible that we need to
// release the borrow count now. (e.g. `U` may have a noop borrow checker so
// dropping the `PyRef<U>` later would noop and leak the borrow we currently hold.)
//
// However it's nontrivial, if `U` itself has a mutable base class `V`,
// then the borrow checker of both `T` and `U` is the shared borrow checker of `V`.
//
// But it's really hard to prove that in the type system, the soundest thing we
// can do is just add a borrow to `U` now and then release the borrow of `T`.
self.inner
.as_super()
.get_class_object()
.borrow_checker()
.try_borrow()
.expect("this object is already borrowed");
self.inner
.get_class_object()
.borrow_checker()
.release_borrow()
};
PyRef {
inner: unsafe {
ManuallyDrop::new(self)
.as_ptr()
.assume_owned_unchecked(py)
.cast_into_unchecked()
},
}
}
/// Borrows a shared reference to `PyRef<T::BaseType>`.
///
/// With the help of this method, you can access attributes and call methods
/// on the superclass without consuming the `PyRef<T>`. This method can also
/// be chained to access the super-superclass (and so on).
///
/// # Examples
/// ```
/// # use pyo3::prelude::*;
/// #[pyclass(subclass)]
/// struct Base {
/// base_name: &'static str,
/// }
/// #[pymethods]
/// impl Base {
/// fn base_name_len(&self) -> usize {
/// self.base_name.len()
/// }
/// }
///
/// #[pyclass(extends=Base)]
/// struct Sub {
/// sub_name: &'static str,
/// }
///
/// #[pymethods]
/// impl Sub {
/// #[new]
/// fn new() -> PyClassInitializer<Self> {
/// PyClassInitializer::from(Base { base_name: "base_name" })
/// .add_subclass(Self { sub_name: "sub_name" })
/// }
/// fn sub_name_len(&self) -> usize {
/// self.sub_name.len()
/// }
/// fn format_name_lengths(slf: PyRef<'_, Self>) -> String {
/// format!("{} {}", slf.as_super().base_name_len(), slf.sub_name_len())
/// }
/// }
/// # Python::attach(|py| {
/// # let sub = Py::new(py, Sub::new()).unwrap();
/// # pyo3::py_run!(py, sub, "assert sub.format_name_lengths() == '9 8'")
/// # });
/// ```
pub fn as_super(&self) -> &PyRef<'p, T::BaseType> {
let ptr = NonNull::from(&self.inner)
// `Bound<T>` has the same layout as `Bound<T::BaseType>`
.cast::<Bound<'p, T::BaseType>>()
// `Bound<T::BaseType>` has the same layout as `PyRef<T::BaseType>`
.cast::<PyRef<'p, T::BaseType>>();
// SAFETY: lifetimes are correctly transferred, and `PyRef<T>` and `PyRef<U>` have the same layout
unsafe { ptr.as_ref() }
}
}
impl<T: PyClass> Deref for PyRef<'_, T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
unsafe { &*self.inner.get_class_object().get_ptr() }
}
}
impl<T: PyClass> Drop for PyRef<'_, T> {
fn drop(&mut self) {
self.inner
.get_class_object()
.borrow_checker()
.release_borrow()
}
}
impl<'py, T: PyClass> IntoPyObject<'py> for PyRef<'py, T> {
type Target = T;
type Output = Bound<'py, T>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT;
fn into_pyobject(self, _py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.inner.clone())
}
}
impl<'a, 'py, T: PyClass> IntoPyObject<'py> for &'a PyRef<'py, T> {
type Target = T;
type Output = Borrowed<'a, 'py, T>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT;
fn into_pyobject(self, _py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.inner.as_borrowed())
}
}
impl<T: PyClass + fmt::Debug> fmt::Debug for PyRef<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
impl<'py, T: PyClass> TryFrom<&Bound<'py, T>> for PyRef<'py, T> {
type Error = PyBorrowError;
#[inline]
fn try_from(value: &Bound<'py, T>) -> Result<Self, Self::Error> {
PyRef::try_borrow(value)
}
}
/// A wrapper type for a mutably borrowed value from a [`Bound<'py, T>`].
///
/// See the [module-level documentation](self) for more information.
#[repr(transparent)]
pub struct PyRefMut<'p, T: PyClass<Frozen = False>> {
// TODO: once the GIL Ref API is removed, consider adding a lifetime parameter to `PyRef` to
// store `Borrowed` here instead, avoiding reference counting overhead.
inner: Bound<'p, T>,
}
impl<'p, T: PyClass<Frozen = False>> PyRefMut<'p, T> {
/// Returns a `Python` token that is bound to the lifetime of the `PyRefMut`.
pub fn py(&self) -> Python<'p> {
self.inner.py()
}
}
impl<T> AsRef<T::BaseType> for PyRefMut<'_, T>
where
T: PyClass<Frozen = False>,
T::BaseType: PyClass<Frozen = False>,
{
fn as_ref(&self) -> &T::BaseType {
PyRefMut::downgrade(self).as_super()
}
}
impl<T> AsMut<T::BaseType> for PyRefMut<'_, T>
where
T: PyClass<Frozen = False>,
T::BaseType: PyClass<Frozen = False>,
{
fn as_mut(&mut self) -> &mut T::BaseType {
self.as_super()
}
}
impl<'py, T: PyClass<Frozen = False>> PyRefMut<'py, T> {
/// Returns the raw FFI pointer represented by self.
///
/// # Safety
///
/// Callers are responsible for ensuring that the pointer does not outlive self.
///
/// The reference is borrowed; callers should not decrease the reference count
/// when they are finished with the pointer.
#[inline]
pub fn as_ptr(&self) -> *mut ffi::PyObject {
self.inner.as_ptr()
}
/// Returns an owned raw FFI pointer represented by self.
///
/// # Safety
///
/// The reference is owned; when finished the caller should either transfer ownership
/// of the pointer or decrease the reference count (e.g. with [`pyo3::ffi::Py_DecRef`](crate::ffi::Py_DecRef)).
#[inline]
pub fn into_ptr(self) -> *mut ffi::PyObject {
self.inner.clone().into_ptr()
}
#[inline]
#[track_caller]
pub(crate) fn borrow(obj: &Bound<'py, T>) -> Self {
Self::try_borrow(obj).expect("Already borrowed")
}
pub(crate) fn try_borrow(obj: &Bound<'py, T>) -> Result<Self, PyBorrowMutError> {
let cell = obj.get_class_object();
cell.ensure_threadsafe();
cell.borrow_checker()
.try_borrow_mut()
.map(|_| Self { inner: obj.clone() })
}
pub(crate) fn downgrade(slf: &Self) -> &PyRef<'py, T> {
let ptr = NonNull::from(slf).cast();
// SAFETY: `PyRefMut<T>` and `PyRef<T>` have the same layout
unsafe { ptr.as_ref() }
}
}
impl<'p, T> PyRefMut<'p, T>
where
T: PyClass<Frozen = False>,
T::BaseType: PyClass<Frozen = False>,
{
/// Gets a `PyRef<T::BaseType>`.
///
/// See [`PyRef::into_super`] for more.
pub fn into_super(self) -> PyRefMut<'p, T::BaseType> {
let py = self.py();
PyRefMut {
inner: unsafe {
ManuallyDrop::new(self)
.as_ptr()
.assume_owned_unchecked(py)
.cast_into_unchecked()
},
}
}
/// Borrows a mutable reference to `PyRefMut<T::BaseType>`.
///
/// With the help of this method, you can mutate attributes and call mutating
/// methods on the superclass without consuming the `PyRefMut<T>`. This method
/// can also be chained to access the super-superclass (and so on).
///
/// See [`PyRef::as_super`] for more.
pub fn as_super(&mut self) -> &mut PyRefMut<'p, T::BaseType> {
let mut ptr = NonNull::from(&mut self.inner)
// `Bound<T>` has the same layout as `Bound<T::BaseType>`
.cast::<Bound<'p, T::BaseType>>()
// `Bound<T::BaseType>` has the same layout as `PyRefMut<T::BaseType>`,
// and the mutable borrow on `self` prevents aliasing
.cast::<PyRefMut<'p, T::BaseType>>();
// SAFETY: lifetimes are correctly transferred, and `PyRefMut<T>` and `PyRefMut<U>` have the same layout
unsafe { ptr.as_mut() }
}
}
impl<T: PyClass<Frozen = False>> Deref for PyRefMut<'_, T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
unsafe { &*self.inner.get_class_object().get_ptr() }
}
}
impl<T: PyClass<Frozen = False>> DerefMut for PyRefMut<'_, T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.inner.get_class_object().get_ptr() }
}
}
impl<T: PyClass<Frozen = False>> Drop for PyRefMut<'_, T> {
fn drop(&mut self) {
self.inner
.get_class_object()
.borrow_checker()
.release_borrow_mut()
}
}
impl<'py, T: PyClass<Frozen = False>> IntoPyObject<'py> for PyRefMut<'py, T> {
type Target = T;
type Output = Bound<'py, T>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT;
fn into_pyobject(self, _py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.inner.clone())
}
}
impl<'a, 'py, T: PyClass<Frozen = False>> IntoPyObject<'py> for &'a PyRefMut<'py, T> {
type Target = T;
type Output = Borrowed<'a, 'py, T>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT;
fn into_pyobject(self, _py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.inner.as_borrowed())
}
}
impl<T: PyClass<Frozen = False> + fmt::Debug> fmt::Debug for PyRefMut<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.deref(), f)
}
}
impl<'py, T: PyClass<Frozen = False>> TryFrom<&Bound<'py, T>> for PyRefMut<'py, T> {
type Error = PyBorrowMutError;
#[inline]
fn try_from(value: &Bound<'py, T>) -> Result<Self, Self::Error> {
PyRefMut::try_borrow(value)
}
}
/// An error type returned by [`Bound::try_borrow`].
///
/// If this error is allowed to bubble up into Python code it will raise a `RuntimeError`.
pub struct PyBorrowError {
_private: (),
}
impl PyBorrowError {
pub(crate) fn new() -> Self {
Self { _private: () }
}
}
impl fmt::Debug for PyBorrowError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PyBorrowError").finish()
}
}
impl fmt::Display for PyBorrowError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt("Already mutably borrowed", f)
}
}
impl From<PyBorrowError> for PyErr {
fn from(other: PyBorrowError) -> Self {
PyRuntimeError::new_err(other.to_string())
}
}
/// An error type returned by [`Bound::try_borrow_mut`].
///
/// If this error is allowed to bubble up into Python code it will raise a `RuntimeError`.
pub struct PyBorrowMutError {
_private: (),
}
impl PyBorrowMutError {
pub(crate) fn new() -> Self {
Self { _private: () }
}
}
impl fmt::Debug for PyBorrowMutError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PyBorrowMutError").finish()
}
}
impl fmt::Display for PyBorrowMutError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt("Already borrowed", f)
}
}
impl From<PyBorrowMutError> for PyErr {
fn from(other: PyBorrowMutError) -> Self {
PyRuntimeError::new_err(other.to_string())
}
}
#[cfg(test)]
#[cfg(feature = "macros")]
mod tests {
use super::*;
#[crate::pyclass(skip_from_py_object)]
#[pyo3(crate = "crate")]
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
struct SomeClass(i32);
#[test]
fn test_as_ptr() {
Python::attach(|py| {
let cell = Bound::new(py, SomeClass(0)).unwrap();
let ptr = cell.as_ptr();
assert_eq!(cell.borrow().as_ptr(), ptr);
assert_eq!(cell.borrow_mut().as_ptr(), ptr);
})
}
#[test]
fn test_into_ptr() {
Python::attach(|py| {
let cell = Bound::new(py, SomeClass(0)).unwrap();
let ptr = cell.as_ptr();
assert_eq!(cell.borrow().into_ptr(), ptr);
unsafe { ffi::Py_DECREF(ptr) };
assert_eq!(cell.borrow_mut().into_ptr(), ptr);
unsafe { ffi::Py_DECREF(ptr) };
})
}
#[crate::pyclass]
#[pyo3(crate = "crate", subclass)]
struct BaseClass {
val1: usize,
}
#[crate::pyclass]
#[pyo3(crate = "crate", extends=BaseClass, subclass)]
struct SubClass {
val2: usize,
}
#[crate::pyclass]
#[pyo3(crate = "crate", extends=SubClass)]
struct SubSubClass {
val3: usize,
}
#[crate::pymethods]
#[pyo3(crate = "crate")]
impl SubSubClass {
#[new]
fn new(py: Python<'_>) -> crate::Py<SubSubClass> {
let init = crate::PyClassInitializer::from(BaseClass { val1: 10 })
.add_subclass(SubClass { val2: 15 })
.add_subclass(SubSubClass { val3: 20 });
crate::Py::new(py, init).expect("allocation error")
}
fn get_values(self_: PyRef<'_, Self>) -> (usize, usize, usize) {
let val1 = self_.as_super().as_super().val1;
let val2 = self_.as_super().val2;
(val1, val2, self_.val3)
}
fn double_values(mut self_: PyRefMut<'_, Self>) {
self_.as_super().as_super().val1 *= 2;
self_.as_super().val2 *= 2;
self_.val3 *= 2;
}
}
#[test]
fn test_pyref_as_super() {
Python::attach(|py| {
let obj = SubSubClass::new(py).into_bound(py);
let pyref = obj.borrow();
assert_eq!(pyref.as_super().as_super().val1, 10);
assert_eq!(pyref.as_super().val2, 15);
assert_eq!(pyref.as_ref().val2, 15); // `as_ref` also works
assert_eq!(pyref.val3, 20);
assert_eq!(SubSubClass::get_values(pyref), (10, 15, 20));
});
}
#[test]
fn test_pyrefmut_as_super() {
Python::attach(|py| {
let obj = SubSubClass::new(py).into_bound(py);
assert_eq!(SubSubClass::get_values(obj.borrow()), (10, 15, 20));
{
let mut pyrefmut = obj.borrow_mut();
assert_eq!(pyrefmut.as_super().as_ref().val1, 10);
pyrefmut.as_super().as_super().val1 -= 5;
pyrefmut.as_super().val2 -= 3;
pyrefmut.as_mut().val2 -= 2; // `as_mut` also works
pyrefmut.val3 -= 5;
}
assert_eq!(SubSubClass::get_values(obj.borrow()), (5, 10, 15));
SubSubClass::double_values(obj.borrow_mut());
assert_eq!(SubSubClass::get_values(obj.borrow()), (10, 20, 30));
});
}
#[test]
fn test_pyrefs_in_python() {
Python::attach(|py| {
let obj = SubSubClass::new(py);
crate::py_run!(py, obj, "assert obj.get_values() == (10, 15, 20)");
crate::py_run!(py, obj, "assert obj.double_values() is None");
crate::py_run!(py, obj, "assert obj.get_values() == (20, 30, 40)");
});
}
#[test]
fn test_into_frozen_super_released_borrow() {
#[crate::pyclass]
#[pyo3(crate = "crate", subclass, frozen)]
struct BaseClass {}
#[crate::pyclass]
#[pyo3(crate = "crate", extends=BaseClass, subclass)]
struct SubClass {}
#[crate::pymethods]
#[pyo3(crate = "crate")]
impl SubClass {
#[new]
fn new(py: Python<'_>) -> Bound<'_, SubClass> {
let init = crate::PyClassInitializer::from(BaseClass {}).add_subclass(SubClass {});
Bound::new(py, init).expect("allocation error")
}
}
Python::attach(|py| {
let obj = SubClass::new(py);
drop(obj.borrow().into_super());
assert!(obj.try_borrow_mut().is_ok());
})
}
#[test]
fn test_into_frozen_super_mutable_base_holds_borrow() {
#[crate::pyclass]
#[pyo3(crate = "crate", subclass)]
struct BaseClass {}
#[crate::pyclass]
#[pyo3(crate = "crate", extends=BaseClass, subclass, frozen)]
struct SubClass {}
#[crate::pyclass]
#[pyo3(crate = "crate", extends=SubClass, subclass)]
struct SubSubClass {}
#[crate::pymethods]
#[pyo3(crate = "crate")]
impl SubSubClass {
#[new]
fn new(py: Python<'_>) -> Bound<'_, SubSubClass> {
let init = crate::PyClassInitializer::from(BaseClass {})
.add_subclass(SubClass {})
.add_subclass(SubSubClass {});
Bound::new(py, init).expect("allocation error")
}
}
Python::attach(|py| {
let obj = SubSubClass::new(py);
let _super_borrow = obj.borrow().into_super();
// the whole object still has an immutable borrow, so we cannot
// borrow any part mutably (the borrowflag is shared)
assert!(obj.try_borrow_mut().is_err());
})
}
}
+925
View File
@@ -0,0 +1,925 @@
#![allow(missing_docs)]
//! Crate-private implementation of PyClassObject
use core::cell::UnsafeCell;
use core::marker::PhantomData;
use core::mem::{offset_of, ManuallyDrop, MaybeUninit};
use core::sync::atomic::{AtomicUsize, Ordering};
use crate::impl_::pyclass::{
PyClassBaseType, PyClassDict, PyClassImpl, PyClassThreadChecker, PyClassWeakRef, PyObjectOffset,
};
use crate::internal::get_slot::{TP_DEALLOC, TP_FREE};
#[cfg(RustPython)]
use crate::sync::PyOnceLock;
use crate::type_object::{PyLayout, PySizedLayout, PyTypeInfo};
use crate::types::PyType;
use crate::{ffi, PyClass, Python};
use crate::types::PyTypeMethods;
use super::{PyBorrowError, PyBorrowMutError};
pub trait PyClassMutability {
// The storage for this inheritance layer. Only the first mutable class in
// an inheritance hierarchy needs to store the borrow flag.
type Storage: PyClassBorrowChecker;
// The borrow flag needed to implement this class' mutability. Empty until
// the first mutable class, at which point it is BorrowChecker and will be
// for all subclasses.
type Checker: PyClassBorrowChecker;
type ImmutableChild: PyClassMutability;
type MutableChild: PyClassMutability;
}
pub struct ImmutableClass(());
pub struct MutableClass(());
pub struct ExtendsMutableAncestor<M: PyClassMutability>(PhantomData<M>);
impl PyClassMutability for ImmutableClass {
type Storage = EmptySlot;
type Checker = EmptySlot;
type ImmutableChild = ImmutableClass;
type MutableChild = MutableClass;
}
impl PyClassMutability for MutableClass {
type Storage = BorrowChecker;
type Checker = BorrowChecker;
type ImmutableChild = ExtendsMutableAncestor<ImmutableClass>;
type MutableChild = ExtendsMutableAncestor<MutableClass>;
}
impl<M: PyClassMutability> PyClassMutability for ExtendsMutableAncestor<M> {
type Storage = EmptySlot;
type Checker = BorrowChecker;
type ImmutableChild = ExtendsMutableAncestor<ImmutableClass>;
type MutableChild = ExtendsMutableAncestor<MutableClass>;
}
#[derive(Debug)]
struct BorrowFlag(AtomicUsize);
impl BorrowFlag {
pub(crate) const UNUSED: usize = 0;
const HAS_MUTABLE_BORROW: usize = usize::MAX;
fn increment(&self) -> Result<(), PyBorrowError> {
// relaxed is OK because we will read the value again in the compare_exchange
let mut value = self.0.load(Ordering::Relaxed);
loop {
if value == BorrowFlag::HAS_MUTABLE_BORROW {
return Err(PyBorrowError { _private: () });
}
match self.0.compare_exchange(
// only increment if the value hasn't changed since the
// last atomic load
value,
value + 1,
// reading the value is happens-after a previous write
// writing the new value is happens-after the previous read
Ordering::AcqRel,
// relaxed is OK here because we're going to try to read again
Ordering::Relaxed,
) {
Ok(..) => {
break Ok(());
}
Err(changed_value) => {
// value changed under us, need to try again
value = changed_value;
}
}
}
}
fn decrement(&self) {
// relaxed load is OK but decrements must happen-before the next read
self.0.fetch_sub(1, Ordering::Release);
}
}
pub struct EmptySlot(());
pub struct BorrowChecker(BorrowFlag);
pub trait PyClassBorrowChecker {
/// Initial value for self
fn new() -> Self
where
Self: Sized;
/// Increments immutable borrow count, if possible
fn try_borrow(&self) -> Result<(), PyBorrowError>;
/// Decrements immutable borrow count
fn release_borrow(&self);
/// Increments mutable borrow count, if possible
fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError>;
/// Decremements mutable borrow count
fn release_borrow_mut(&self);
}
impl PyClassBorrowChecker for EmptySlot {
#[inline]
fn new() -> Self {
EmptySlot(())
}
#[inline]
fn try_borrow(&self) -> Result<(), PyBorrowError> {
Ok(())
}
#[inline]
fn release_borrow(&self) {}
#[inline]
fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError> {
unreachable!()
}
#[inline]
fn release_borrow_mut(&self) {
unreachable!()
}
}
impl PyClassBorrowChecker for BorrowChecker {
#[inline]
fn new() -> Self {
Self(BorrowFlag(AtomicUsize::new(BorrowFlag::UNUSED)))
}
fn try_borrow(&self) -> Result<(), PyBorrowError> {
self.0.increment()
}
fn release_borrow(&self) {
self.0.decrement();
}
fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError> {
let flag = &self.0;
match flag.0.compare_exchange(
// only allowed to transition to mutable borrow if the reference is
// currently unused
BorrowFlag::UNUSED,
BorrowFlag::HAS_MUTABLE_BORROW,
// On success, reading the flag and updating its state are an atomic
// operation
Ordering::AcqRel,
// It doesn't matter precisely when the failure gets turned
// into an error
Ordering::Relaxed,
) {
Ok(..) => Ok(()),
Err(..) => Err(PyBorrowMutError { _private: () }),
}
}
fn release_borrow_mut(&self) {
self.0 .0.store(BorrowFlag::UNUSED, Ordering::Release)
}
}
pub trait GetBorrowChecker<T: PyClassImpl> {
fn borrow_checker(
class_object: &T::Layout,
) -> &<T::PyClassMutability as PyClassMutability>::Checker;
}
impl<T: PyClassImpl<PyClassMutability = Self>> GetBorrowChecker<T> for MutableClass {
fn borrow_checker(class_object: &T::Layout) -> &BorrowChecker {
&class_object.contents().borrow_checker
}
}
impl<T: PyClassImpl<PyClassMutability = Self>> GetBorrowChecker<T> for ImmutableClass {
fn borrow_checker(class_object: &T::Layout) -> &EmptySlot {
&class_object.contents().borrow_checker
}
}
impl<T: PyClassImpl<PyClassMutability = Self>, M: PyClassMutability> GetBorrowChecker<T>
for ExtendsMutableAncestor<M>
where
T::BaseType: PyClassImpl + PyClassBaseType<LayoutAsBase = <T::BaseType as PyClassImpl>::Layout>,
<T::BaseType as PyClassImpl>::PyClassMutability: PyClassMutability<Checker = BorrowChecker>,
{
fn borrow_checker(class_object: &T::Layout) -> &BorrowChecker {
<<T::BaseType as PyClassImpl>::PyClassMutability as GetBorrowChecker<T::BaseType>>::borrow_checker(class_object.ob_base())
}
}
/// Base layout of PyClassObject with a known sized base type.
/// Corresponds to [PyObject](https://docs.python.org/3/c-api/structures.html#c.PyObject) from the C API.
#[doc(hidden)]
#[repr(C)]
pub struct PyClassObjectBase<T> {
ob_base: T,
}
unsafe impl<T, U> PyLayout<T> for PyClassObjectBase<U> where U: PySizedLayout<T> {}
impl<T, U> PyClassObjectBaseLayout<T> for PyClassObjectBase<U>
where
U: PySizedLayout<T>,
T: PyTypeInfo,
{
fn ensure_threadsafe(&self) {}
fn check_threadsafe(&self) -> Result<(), PyBorrowError> {
Ok(())
}
unsafe fn tp_dealloc(py: Python<'_>, slf: *mut ffi::PyObject) {
unsafe { tp_dealloc(slf, &T::type_object(py)) };
}
}
/// Base layout of PyClassObject with an unknown sized base type.
/// Corresponds to [PyVarObject](https://docs.python.org/3/c-api/structures.html#c.PyVarObject) from the C API.
#[doc(hidden)]
#[repr(C)]
pub struct PyVariableClassObjectBase {
ob_base: ffi::PyVarObject,
}
unsafe impl<T> PyLayout<T> for PyVariableClassObjectBase {}
impl<T: PyTypeInfo> PyClassObjectBaseLayout<T> for PyVariableClassObjectBase {
fn ensure_threadsafe(&self) {}
fn check_threadsafe(&self) -> Result<(), PyBorrowError> {
Ok(())
}
unsafe fn tp_dealloc(py: Python<'_>, slf: *mut ffi::PyObject) {
unsafe { tp_dealloc(slf, &T::type_object(py)) };
}
}
/// Implementation of tp_dealloc.
/// # Safety
/// - `slf` must be a valid pointer to an instance of the type at `type_obj` or a subclass.
/// - `slf` must not be used after this call (as it will be freed).
unsafe fn tp_dealloc(slf: *mut ffi::PyObject, type_obj: &crate::Bound<'_, PyType>) {
let py = type_obj.py();
unsafe {
// FIXME: there is potentially subtle issues here if the base is overwritten
// at runtime? To be investigated.
let type_ptr = type_obj.as_type_ptr();
let actual_type_ptr = ffi::Py_TYPE(slf);
// For heap types, instances must decref the type object when they
// are deallocated, so we create a bound from a borrowed pointer as
// as if it was an owned pointer. In this way, when the bound is dropped,
// it will decref the type object.
debug_assert!(ffi::PyType_HasFeature(actual_type_ptr, ffi::Py_TPFLAGS_HEAPTYPE) != 0);
let actual_type = cfg_select! {
not(PyPy) => crate::Bound::from_owned_ptr(py, actual_type_ptr as *mut ffi::PyObject)
.cast_into_unchecked::<PyType>(),
// See https://github.com/pypy/pypy/issues/5555 - it seems that PyPy does not
// support the CPython semantics properly, so we avoid taking ownership of the
// type object on PyPy.
//
// TODO: If the PyPy bug is fixed we should remove this workaround and just create
// a `Bound` as above.
PyPy => crate::Borrowed::from_ptr(py, actual_type_ptr as *mut ffi::PyObject)
.cast_unchecked::<PyType>(),
};
// For `#[pyclass]` types which inherit from PyAny, we can just call tp_free
#[cfg(not(RustPython))]
let base_object_type_ptr = &raw const ffi::PyBaseObject_Type;
#[cfg(RustPython)]
let base_object_type_ptr = {
static TYPE: PyOnceLock<crate::Py<PyType>> = PyOnceLock::new();
TYPE.import(py, "builtins", "object").unwrap().as_type_ptr()
};
if core::ptr::eq(type_ptr, base_object_type_ptr) {
let tp_free = actual_type
.get_slot(TP_FREE)
.expect("PyBaseObject_Type should have tp_free");
tp_free(slf.cast());
}
// More complex native types (e.g. `extends=PyDict`) require calling the base's dealloc.
// FIXME: should this be using actual_type.tp_dealloc?
else if let Some(dealloc) = type_obj.get_slot(TP_DEALLOC) {
// Before CPython 3.11 BaseException_dealloc would use Py_GC_UNTRACK which
// assumes the exception is currently GC tracked, so we have to re-track
// before calling the dealloc so that it can safely call Py_GC_UNTRACK.
#[cfg(not(any(Py_3_11, PyPy)))]
if ffi::PyType_FastSubclass(type_ptr, ffi::Py_TPFLAGS_BASE_EXC_SUBCLASS) == 1 {
ffi::PyObject_GC_Track(slf.cast());
}
dealloc(slf);
} else {
type_obj.get_slot(TP_FREE).expect("type missing tp_free")(slf.cast());
}
// Cause the reference to the type to be decrefed for heap types, which
// is necessary to avoid a reference leak.
#[cfg_attr(
PyPy,
expect(dropping_copy_types, reason = "see PyPy workaround for decref above")
)]
drop(actual_type);
}
}
/// functionality common to all PyObjects regardless of the layout
#[doc(hidden)]
pub trait PyClassObjectBaseLayout<T>: PyLayout<T> {
fn ensure_threadsafe(&self);
fn check_threadsafe(&self) -> Result<(), PyBorrowError>;
/// Implementation of tp_dealloc.
/// # Safety
/// - slf must be a valid pointer to an instance of a T or a subclass.
/// - slf must not be used after this call (as it will be freed).
unsafe fn tp_dealloc(py: Python<'_>, slf: *mut ffi::PyObject);
}
/// Functionality required for creating and managing the memory associated with a pyclass annotated struct.
#[doc(hidden)]
#[diagnostic::on_unimplemented(
message = "the class layout is not valid",
label = "required for `#[pyclass(extends=...)]`",
note = "the python version being built against influences which layouts are valid"
)]
pub trait PyClassObjectLayout<T: PyClassImpl>: PyClassObjectBaseLayout<T> {
/// Gets the offset of the contents from the start of the struct in bytes.
const CONTENTS_OFFSET: PyObjectOffset;
/// Used to set `PyType_Spec::basicsize`
/// ([docs](https://docs.python.org/3/c-api/type.html#c.PyType_Spec.basicsize))
const BASIC_SIZE: ffi::Py_ssize_t;
/// Gets the offset of the dictionary from the start of the struct in bytes.
const DICT_OFFSET: PyObjectOffset;
/// Gets the offset of the weakref list from the start of the struct in bytes.
const WEAKLIST_OFFSET: PyObjectOffset;
/// Obtain a pointer to the contents of an uninitialized PyObject of this type.
///
/// SAFETY: `obj` must have the layout that the implementation is expecting
unsafe fn contents_uninit(
obj: *mut ffi::PyObject,
) -> *mut MaybeUninit<PyClassObjectContents<T>>;
/// Obtain a reference to the structure that contains the pyclass struct and associated metadata.
fn contents(&self) -> &PyClassObjectContents<T>;
/// Obtain a mutable reference to the structure that contains the pyclass struct and associated metadata.
fn contents_mut(&mut self) -> &mut PyClassObjectContents<T>;
/// Obtain a pointer to the pyclass struct.
fn get_ptr(&self) -> *mut T;
/// obtain a reference to the data at the start of the PyObject.
fn ob_base(&self) -> &<T::BaseType as PyClassBaseType>::LayoutAsBase;
fn borrow_checker(&self) -> &<T::PyClassMutability as PyClassMutability>::Checker;
}
#[repr(C)]
pub struct PyClassObjectContents<T: PyClassImpl> {
pub(crate) value: ManuallyDrop<UnsafeCell<T>>,
pub(crate) borrow_checker: <T::PyClassMutability as PyClassMutability>::Storage,
pub(crate) thread_checker: T::ThreadChecker,
pub(crate) dict: T::Dict,
pub(crate) weakref: T::WeakRef,
}
impl<T: PyClassImpl> PyClassObjectContents<T> {
pub(crate) fn new(init: T) -> Self {
PyClassObjectContents {
value: ManuallyDrop::new(UnsafeCell::new(init)),
borrow_checker: <T::PyClassMutability as PyClassMutability>::Storage::new(),
thread_checker: T::ThreadChecker::new(),
dict: T::Dict::INIT,
weakref: T::WeakRef::INIT,
}
}
unsafe fn dealloc(&mut self, py: Python<'_>, py_object: *mut ffi::PyObject) {
if self.thread_checker.can_drop(py) {
unsafe { ManuallyDrop::drop(&mut self.value) };
}
self.dict.release_dict(py);
unsafe { self.weakref.clear_weakrefs(py_object, py) };
}
}
/// The layout of a PyClassObject with a known sized base class.
#[repr(C)]
pub struct PyStaticClassObject<T: PyClassImpl> {
ob_base: <T::BaseType as PyClassBaseType>::LayoutAsBase,
contents: PyClassObjectContents<T>,
}
impl<T: PyClassImpl<Layout = Self>> PyClassObjectLayout<T> for PyStaticClassObject<T> {
/// Gets the offset of the contents from the start of the struct in bytes.
const CONTENTS_OFFSET: PyObjectOffset = {
let offset = offset_of!(Self, contents);
// Py_ssize_t may not be equal to isize on all platforms
assert!(offset <= ffi::Py_ssize_t::MAX as usize);
PyObjectOffset::Absolute(offset as ffi::Py_ssize_t)
};
const BASIC_SIZE: ffi::Py_ssize_t = {
let size = core::mem::size_of::<Self>();
assert!(size <= ffi::Py_ssize_t::MAX as usize);
size as _
};
const DICT_OFFSET: PyObjectOffset = {
let offset = offset_of!(PyStaticClassObject<T>, contents)
+ offset_of!(PyClassObjectContents<T>, dict);
assert!(offset <= ffi::Py_ssize_t::MAX as usize);
PyObjectOffset::Absolute(offset as _)
};
const WEAKLIST_OFFSET: PyObjectOffset = {
let offset = offset_of!(PyStaticClassObject<T>, contents)
+ offset_of!(PyClassObjectContents<T>, weakref);
assert!(offset <= ffi::Py_ssize_t::MAX as usize);
PyObjectOffset::Absolute(offset as _)
};
unsafe fn contents_uninit(
obj: *mut ffi::PyObject,
) -> *mut MaybeUninit<PyClassObjectContents<T>> {
#[repr(C)]
struct PartiallyInitializedClassObject<T: PyClassImpl> {
_ob_base: <T::BaseType as PyClassBaseType>::LayoutAsBase,
contents: MaybeUninit<PyClassObjectContents<T>>,
}
let obj = obj.cast::<PartiallyInitializedClassObject<T>>();
unsafe { &raw mut (*obj).contents }
}
fn contents(&self) -> &PyClassObjectContents<T> {
&self.contents
}
fn contents_mut(&mut self) -> &mut PyClassObjectContents<T> {
&mut self.contents
}
fn get_ptr(&self) -> *mut T {
self.contents.value.get()
}
fn ob_base(&self) -> &<T::BaseType as PyClassBaseType>::LayoutAsBase {
&self.ob_base
}
fn borrow_checker(&self) -> &<T::PyClassMutability as PyClassMutability>::Checker {
T::PyClassMutability::borrow_checker(self)
}
}
unsafe impl<T: PyClassImpl> PyLayout<T> for PyStaticClassObject<T> {}
impl<T: PyClass> PySizedLayout<T> for PyStaticClassObject<T> {}
impl<T: PyClassImpl<Layout = Self>> PyClassObjectBaseLayout<T> for PyStaticClassObject<T>
where
<T::BaseType as PyClassBaseType>::LayoutAsBase: PyClassObjectBaseLayout<T::BaseType>,
{
fn ensure_threadsafe(&self) {
self.contents.thread_checker.ensure();
self.ob_base.ensure_threadsafe();
}
fn check_threadsafe(&self) -> Result<(), PyBorrowError> {
if !self.contents.thread_checker.check() {
return Err(PyBorrowError { _private: () });
}
self.ob_base.check_threadsafe()
}
unsafe fn tp_dealloc(py: Python<'_>, slf: *mut ffi::PyObject) {
// Safety: Python only calls tp_dealloc when no references to the object remain.
let class_object = unsafe { &mut *(slf.cast::<T::Layout>()) };
unsafe { class_object.contents_mut().dealloc(py, slf) };
unsafe { <T::BaseType as PyClassBaseType>::LayoutAsBase::tp_dealloc(py, slf) }
}
}
/// A layout for a PyClassObject with an unknown sized base type.
///
/// Utilises [PEP-697](https://peps.python.org/pep-0697/)
#[doc(hidden)]
#[repr(C)]
pub struct PyVariableClassObject<T: PyClassImpl> {
ob_base: <T::BaseType as PyClassBaseType>::LayoutAsBase,
}
#[cfg(Py_3_12)]
impl<T: PyClass<Layout = Self>> PyVariableClassObject<T> {
/// # Safety
/// - `obj` must have the layout that the implementation is expecting
/// - thread must be attached to the interpreter
unsafe fn get_contents_of_obj(
obj: *mut ffi::PyObject,
) -> *mut MaybeUninit<PyClassObjectContents<T>> {
// TODO: it would be nice to eventually avoid coupling to the PyO3 statics here, maybe using
// 3.14's PyType_GetBaseByToken, to support PEP 587 / multiple interpreters better
// SAFETY: caller guarantees attached to the interpreter
let type_obj = T::type_object_raw(unsafe { Python::assume_attached() });
let pointer = unsafe { ffi::PyObject_GetTypeData(obj, type_obj) };
pointer.cast()
}
fn get_contents_ptr(&self) -> *mut PyClassObjectContents<T> {
unsafe {
Self::get_contents_of_obj(self as *const PyVariableClassObject<T> as *mut ffi::PyObject)
}
.cast()
}
}
#[cfg(Py_3_12)]
impl<T: PyClass<Layout = Self>> PyClassObjectLayout<T> for PyVariableClassObject<T> {
/// Gets the offset of the contents from the start of the struct in bytes.
const CONTENTS_OFFSET: PyObjectOffset = PyObjectOffset::Relative(0);
const BASIC_SIZE: ffi::Py_ssize_t = {
let size = core::mem::size_of::<PyClassObjectContents<T>>();
assert!(size <= ffi::Py_ssize_t::MAX as usize);
// negative to indicate 'extra' space that cpython will allocate for us
-(size as ffi::Py_ssize_t)
};
const DICT_OFFSET: PyObjectOffset = {
let offset = offset_of!(PyClassObjectContents<T>, dict);
assert!(offset <= ffi::Py_ssize_t::MAX as usize);
PyObjectOffset::Relative(offset as _)
};
const WEAKLIST_OFFSET: PyObjectOffset = {
let offset = offset_of!(PyClassObjectContents<T>, weakref);
assert!(offset <= ffi::Py_ssize_t::MAX as usize);
PyObjectOffset::Relative(offset as _)
};
unsafe fn contents_uninit(
obj: *mut ffi::PyObject,
) -> *mut MaybeUninit<PyClassObjectContents<T>> {
unsafe { Self::get_contents_of_obj(obj) }
}
fn get_ptr(&self) -> *mut T {
self.contents().value.get()
}
fn ob_base(&self) -> &<T::BaseType as PyClassBaseType>::LayoutAsBase {
&self.ob_base
}
fn contents(&self) -> &PyClassObjectContents<T> {
unsafe { self.get_contents_ptr().cast_const().as_ref() }
.expect("should be able to cast PyClassObjectContents pointer")
}
fn contents_mut(&mut self) -> &mut PyClassObjectContents<T> {
unsafe { self.get_contents_ptr().as_mut() }
.expect("should be able to cast PyClassObjectContents pointer")
}
fn borrow_checker(&self) -> &<T::PyClassMutability as PyClassMutability>::Checker {
T::PyClassMutability::borrow_checker(self)
}
}
unsafe impl<T: PyClassImpl> PyLayout<T> for PyVariableClassObject<T> {}
#[cfg(Py_3_12)]
impl<T: PyClass<Layout = Self>> PyClassObjectBaseLayout<T> for PyVariableClassObject<T>
where
<T::BaseType as PyClassBaseType>::LayoutAsBase: PyClassObjectBaseLayout<T::BaseType>,
{
fn ensure_threadsafe(&self) {
self.contents().thread_checker.ensure();
self.ob_base.ensure_threadsafe();
}
fn check_threadsafe(&self) -> Result<(), PyBorrowError> {
if !self.contents().thread_checker.check() {
return Err(PyBorrowError { _private: () });
}
self.ob_base.check_threadsafe()
}
unsafe fn tp_dealloc(py: Python<'_>, slf: *mut ffi::PyObject) {
// Safety: Python only calls tp_dealloc when no references to the object remain.
let class_object = unsafe { &mut *(slf.cast::<T::Layout>()) };
unsafe { class_object.contents_mut().dealloc(py, slf) };
unsafe { <T::BaseType as PyClassBaseType>::LayoutAsBase::tp_dealloc(py, slf) }
}
}
#[cfg(test)]
#[cfg(feature = "macros")]
mod tests {
use super::*;
use crate::prelude::*;
use crate::pyclass::boolean_struct::{False, True};
#[pyclass(crate = "crate", subclass)]
struct MutableBase;
#[pyclass(crate = "crate", extends = MutableBase, subclass)]
struct MutableChildOfMutableBase;
#[pyclass(crate = "crate", extends = MutableBase, frozen, subclass)]
struct ImmutableChildOfMutableBase;
#[pyclass(crate = "crate", extends = MutableChildOfMutableBase)]
struct MutableChildOfMutableChildOfMutableBase;
#[pyclass(crate = "crate", extends = ImmutableChildOfMutableBase)]
struct MutableChildOfImmutableChildOfMutableBase;
#[pyclass(crate = "crate", extends = MutableChildOfMutableBase, frozen)]
struct ImmutableChildOfMutableChildOfMutableBase;
#[pyclass(crate = "crate", extends = ImmutableChildOfMutableBase, frozen)]
struct ImmutableChildOfImmutableChildOfMutableBase;
#[pyclass(crate = "crate", frozen, subclass)]
struct ImmutableBase;
#[pyclass(crate = "crate", extends = ImmutableBase, subclass)]
struct MutableChildOfImmutableBase;
#[pyclass(crate = "crate", extends = ImmutableBase, frozen, subclass)]
struct ImmutableChildOfImmutableBase;
#[pyclass(crate = "crate", extends = MutableChildOfImmutableBase)]
struct MutableChildOfMutableChildOfImmutableBase;
#[pyclass(crate = "crate", extends = ImmutableChildOfImmutableBase)]
struct MutableChildOfImmutableChildOfImmutableBase;
#[pyclass(crate = "crate", extends = MutableChildOfImmutableBase, frozen)]
struct ImmutableChildOfMutableChildOfImmutableBase;
#[pyclass(crate = "crate", extends = ImmutableChildOfImmutableBase, frozen)]
struct ImmutableChildOfImmutableChildOfImmutableBase;
#[pyclass(crate = "crate", subclass)]
struct BaseWithoutData;
#[pyclass(crate = "crate", subclass)]
struct BaseWithData(#[allow(unused)] u64);
#[pyclass(crate = "crate", extends = BaseWithData)]
struct ChildWithData(#[allow(unused)] u64);
#[pyclass(crate = "crate", extends = BaseWithData)]
struct ChildWithoutData;
#[test]
fn test_inherited_size() {
#[cfg(all(Py_LIMITED_API, Py_GIL_DISABLED))]
type ClassObject<T> = PyVariableClassObject<T>;
#[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
type ClassObject<T> = PyStaticClassObject<T>;
let base_without_data_size = ClassObject::<BaseWithoutData>::BASIC_SIZE;
let base_with_data_size = ClassObject::<BaseWithData>::BASIC_SIZE;
let child_without_data_size = ClassObject::<ChildWithoutData>::BASIC_SIZE;
let child_with_data_size = ClassObject::<ChildWithData>::BASIC_SIZE;
#[cfg(all(Py_LIMITED_API, Py_GIL_DISABLED))]
{
assert!(base_without_data_size < 0); // negative indicates variable sized
assert!(base_with_data_size < base_without_data_size);
assert_eq!(child_without_data_size, 0);
assert_eq!(
base_with_data_size - base_without_data_size,
child_with_data_size
);
}
#[cfg(not(all(Py_LIMITED_API, Py_GIL_DISABLED)))]
{
assert!(base_without_data_size > 0);
assert!(base_with_data_size > base_without_data_size);
assert_eq!(base_with_data_size, child_without_data_size);
assert!(base_with_data_size < child_with_data_size);
}
}
fn assert_mutable<T: PyClass<Frozen = False, PyClassMutability = MutableClass>>() {}
fn assert_immutable<T: PyClass<Frozen = True, PyClassMutability = ImmutableClass>>() {}
fn assert_mutable_with_mutable_ancestor<
T: PyClass<Frozen = False, PyClassMutability = ExtendsMutableAncestor<MutableClass>>,
>() {
}
fn assert_immutable_with_mutable_ancestor<
T: PyClass<Frozen = True, PyClassMutability = ExtendsMutableAncestor<ImmutableClass>>,
>() {
}
#[test]
fn test_inherited_mutability() {
// mutable base
assert_mutable::<MutableBase>();
// children of mutable base have a mutable ancestor
assert_mutable_with_mutable_ancestor::<MutableChildOfMutableBase>();
assert_immutable_with_mutable_ancestor::<ImmutableChildOfMutableBase>();
// grandchildren of mutable base have a mutable ancestor
assert_mutable_with_mutable_ancestor::<MutableChildOfMutableChildOfMutableBase>();
assert_mutable_with_mutable_ancestor::<MutableChildOfImmutableChildOfMutableBase>();
assert_immutable_with_mutable_ancestor::<ImmutableChildOfMutableChildOfMutableBase>();
assert_immutable_with_mutable_ancestor::<ImmutableChildOfImmutableChildOfMutableBase>();
// immutable base and children
assert_immutable::<ImmutableBase>();
assert_immutable::<ImmutableChildOfImmutableBase>();
assert_immutable::<ImmutableChildOfImmutableChildOfImmutableBase>();
// mutable children of immutable at any level are simply mutable
assert_mutable::<MutableChildOfImmutableBase>();
assert_mutable::<MutableChildOfImmutableChildOfImmutableBase>();
// children of the mutable child display this property
assert_mutable_with_mutable_ancestor::<MutableChildOfMutableChildOfImmutableBase>();
assert_immutable_with_mutable_ancestor::<ImmutableChildOfMutableChildOfImmutableBase>();
}
#[test]
fn test_mutable_borrow_prevents_further_borrows() {
Python::attach(|py| {
let mmm = Py::new(
py,
PyClassInitializer::from(MutableBase)
.add_subclass(MutableChildOfMutableBase)
.add_subclass(MutableChildOfMutableChildOfMutableBase),
)
.unwrap();
let mmm_bound: &Bound<'_, MutableChildOfMutableChildOfMutableBase> = mmm.bind(py);
let mmm_refmut = mmm_bound.borrow_mut();
// Cannot take any other mutable or immutable borrows whilst the object is borrowed mutably
assert!(mmm_bound
.extract::<PyRef<'_, MutableChildOfMutableChildOfMutableBase>>()
.is_err());
assert!(mmm_bound
.extract::<PyRef<'_, MutableChildOfMutableBase>>()
.is_err());
assert!(mmm_bound.extract::<PyRef<'_, MutableBase>>().is_err());
assert!(mmm_bound
.extract::<PyRefMut<'_, MutableChildOfMutableChildOfMutableBase>>()
.is_err());
assert!(mmm_bound
.extract::<PyRefMut<'_, MutableChildOfMutableBase>>()
.is_err());
assert!(mmm_bound.extract::<PyRefMut<'_, MutableBase>>().is_err());
// With the borrow dropped, all other borrow attempts will succeed
drop(mmm_refmut);
assert!(mmm_bound
.extract::<PyRef<'_, MutableChildOfMutableChildOfMutableBase>>()
.is_ok());
assert!(mmm_bound
.extract::<PyRef<'_, MutableChildOfMutableBase>>()
.is_ok());
assert!(mmm_bound.extract::<PyRef<'_, MutableBase>>().is_ok());
assert!(mmm_bound
.extract::<PyRefMut<'_, MutableChildOfMutableChildOfMutableBase>>()
.is_ok());
assert!(mmm_bound
.extract::<PyRefMut<'_, MutableChildOfMutableBase>>()
.is_ok());
assert!(mmm_bound.extract::<PyRefMut<'_, MutableBase>>().is_ok());
})
}
#[test]
fn test_immutable_borrows_prevent_mutable_borrows() {
Python::attach(|py| {
let mmm = Py::new(
py,
PyClassInitializer::from(MutableBase)
.add_subclass(MutableChildOfMutableBase)
.add_subclass(MutableChildOfMutableChildOfMutableBase),
)
.unwrap();
let mmm_bound: &Bound<'_, MutableChildOfMutableChildOfMutableBase> = mmm.bind(py);
let mmm_refmut = mmm_bound.borrow();
// Further immutable borrows are ok
assert!(mmm_bound
.extract::<PyRef<'_, MutableChildOfMutableChildOfMutableBase>>()
.is_ok());
assert!(mmm_bound
.extract::<PyRef<'_, MutableChildOfMutableBase>>()
.is_ok());
assert!(mmm_bound.extract::<PyRef<'_, MutableBase>>().is_ok());
// Further mutable borrows are not ok
assert!(mmm_bound
.extract::<PyRefMut<'_, MutableChildOfMutableChildOfMutableBase>>()
.is_err());
assert!(mmm_bound
.extract::<PyRefMut<'_, MutableChildOfMutableBase>>()
.is_err());
assert!(mmm_bound.extract::<PyRefMut<'_, MutableBase>>().is_err());
// With the borrow dropped, all mutable borrow attempts will succeed
drop(mmm_refmut);
assert!(mmm_bound
.extract::<PyRefMut<'_, MutableChildOfMutableChildOfMutableBase>>()
.is_ok());
assert!(mmm_bound
.extract::<PyRefMut<'_, MutableChildOfMutableBase>>()
.is_ok());
assert!(mmm_bound.extract::<PyRefMut<'_, MutableBase>>().is_ok());
})
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn test_thread_safety() {
#[crate::pyclass(crate = "crate")]
struct MyClass {
x: u64,
}
Python::attach(|py| {
let inst = Py::new(py, MyClass { x: 0 }).unwrap();
let total_modifications = py.detach(|| {
std::thread::scope(|s| {
// Spawn a bunch of threads all racing to write to
// the same instance of `MyClass`.
let threads = (0..10)
.map(|_| {
s.spawn(|| {
Python::attach(|py| {
// Each thread records its own view of how many writes it made
let mut local_modifications = 0;
for _ in 0..100 {
if let Ok(mut i) = inst.try_borrow_mut(py) {
i.x += 1;
local_modifications += 1;
}
}
local_modifications
})
})
})
.collect::<Vec<_>>();
// Sum up the total number of writes made by all threads
threads.into_iter().map(|t| t.join().unwrap()).sum::<u64>()
})
});
// If the implementation is free of data races, the total number of writes
// should match the final value of `x`.
assert_eq!(total_modifications, inst.borrow(py).x);
});
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn test_thread_safety_2() {
struct SyncUnsafeCell<T>(UnsafeCell<T>);
unsafe impl<T> Sync for SyncUnsafeCell<T> {}
impl<T> SyncUnsafeCell<T> {
fn get(&self) -> *mut T {
self.0.get()
}
}
let data = SyncUnsafeCell(UnsafeCell::new(0));
let data2 = SyncUnsafeCell(UnsafeCell::new(0));
let borrow_checker = BorrowChecker(BorrowFlag(AtomicUsize::new(BorrowFlag::UNUSED)));
std::thread::scope(|s| {
s.spawn(|| {
for _ in 0..1_000_000 {
if borrow_checker.try_borrow_mut().is_ok() {
// thread 1 writes to both values during the mutable borrow
unsafe { *data.get() += 1 };
unsafe { *data2.get() += 1 };
borrow_checker.release_borrow_mut();
}
}
});
s.spawn(|| {
for _ in 0..1_000_000 {
if borrow_checker.try_borrow().is_ok() {
// if the borrow checker is working correctly, it should be impossible
// for thread 2 to observe a difference in the two values
assert_eq!(unsafe { *data.get() }, unsafe { *data2.get() });
borrow_checker.release_borrow();
}
}
});
});
}
}
+141
View File
@@ -0,0 +1,141 @@
//! `PyClass` and related traits.
use crate::{ffi, impl_::pyclass::PyClassImpl, PyTypeInfo};
use core::{cmp::Ordering, ffi::c_int};
mod create_type_object;
mod gc;
mod guard;
pub(crate) use self::create_type_object::{create_type_object, PyClassTypeObject};
pub use self::gc::{PyTraverseError, PyVisit};
pub use self::guard::{
PyClassGuard, PyClassGuardError, PyClassGuardMap, PyClassGuardMut, PyClassGuardMutError,
PyClassGuardMutSuper,
};
/// Types that can be used as Python classes.
///
/// The `#[pyclass]` attribute implements this trait for your Rust struct -
/// you shouldn't implement this trait directly.
pub trait PyClass: PyTypeInfo + PyClassImpl {
/// Name of the class.
///
/// This can be set via `#[pyclass(name = "...")]`, otherwise it defaults to the Rust type name.
const NAME: &'static str;
/// Whether the pyclass is frozen.
///
/// This can be enabled via `#[pyclass(frozen)]`.
type Frozen: Frozen;
}
/// Operators for the `__richcmp__` method
#[derive(Debug, Clone, Copy)]
pub enum CompareOp {
/// The *less than* operator.
Lt = ffi::Py_LT as isize,
/// The *less than or equal to* operator.
Le = ffi::Py_LE as isize,
/// The equality operator.
Eq = ffi::Py_EQ as isize,
/// The *not equal to* operator.
Ne = ffi::Py_NE as isize,
/// The *greater than* operator.
Gt = ffi::Py_GT as isize,
/// The *greater than or equal to* operator.
Ge = ffi::Py_GE as isize,
}
impl CompareOp {
/// Conversion from the C enum.
pub fn from_raw(op: c_int) -> Option<Self> {
match op {
ffi::Py_LT => Some(CompareOp::Lt),
ffi::Py_LE => Some(CompareOp::Le),
ffi::Py_EQ => Some(CompareOp::Eq),
ffi::Py_NE => Some(CompareOp::Ne),
ffi::Py_GT => Some(CompareOp::Gt),
ffi::Py_GE => Some(CompareOp::Ge),
_ => None,
}
}
/// Returns if a Rust [`core::cmp::Ordering`] matches this ordering query.
///
/// Usage example:
///
/// ```rust,no_run
/// # use pyo3::prelude::*;
/// # use pyo3::class::basic::CompareOp;
///
/// #[pyclass]
/// struct Size {
/// size: usize,
/// }
///
/// #[pymethods]
/// impl Size {
/// fn __richcmp__(&self, other: &Size, op: CompareOp) -> bool {
/// op.matches(self.size.cmp(&other.size))
/// }
/// }
/// ```
pub fn matches(&self, result: Ordering) -> bool {
match self {
CompareOp::Eq => result == Ordering::Equal,
CompareOp::Ne => result != Ordering::Equal,
CompareOp::Lt => result == Ordering::Less,
CompareOp::Le => result != Ordering::Greater,
CompareOp::Gt => result == Ordering::Greater,
CompareOp::Ge => result != Ordering::Less,
}
}
}
/// A workaround for [associated const equality](https://github.com/rust-lang/rust/issues/92827).
///
/// This serves to have True / False values in the [`PyClass`] trait's `Frozen` type.
#[doc(hidden)]
pub mod boolean_struct {
pub(crate) mod private {
use super::*;
/// A way to "seal" the boolean traits.
pub trait Boolean {
const VALUE: bool;
}
impl Boolean for True {
const VALUE: bool = true;
}
impl Boolean for False {
const VALUE: bool = false;
}
}
pub struct True(());
pub struct False(());
}
/// A trait which is used to describe whether a `#[pyclass]` is frozen.
#[doc(hidden)]
pub trait Frozen: boolean_struct::private::Boolean {}
impl Frozen for boolean_struct::True {}
impl Frozen for boolean_struct::False {}
mod tests {
#[test]
fn test_compare_op_matches() {
use super::CompareOp;
use core::cmp::Ordering;
assert!(CompareOp::Eq.matches(Ordering::Equal));
assert!(CompareOp::Ne.matches(Ordering::Less));
assert!(CompareOp::Ge.matches(Ordering::Greater));
assert!(CompareOp::Gt.matches(Ordering::Greater));
assert!(CompareOp::Le.matches(Ordering::Equal));
assert!(CompareOp::Lt.matches(Ordering::Less));
}
}
+831
View File
@@ -0,0 +1,831 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
use crate::exceptions::PyAttributeError;
use crate::impl_::pymethods::{Deleter, PyDeleterDef};
use crate::platform::HashMap;
#[cfg(not(Py_3_10))]
use crate::types::typeobject::PyTypeMethods;
use crate::{
exceptions::PyTypeError,
ffi,
ffi_ptr_ext::FfiPtrExt,
impl_::{
pyclass::{
assign_sequence_item_from_mapping, get_sequence_item_from_mapping, tp_dealloc,
tp_dealloc_with_gc, PyClassImpl, PyClassItemsIter, PyObjectOffset,
},
pymethods::{
synthesized_clear, synthesized_traverse, Getter, PyGetterDef, PyMethodDefType,
PySetterDef, Setter,
},
trampoline::trampoline,
},
pycell::impl_::PyClassObjectLayout,
types::PyType,
Py, PyClass, PyResult, PyTypeInfo, Python,
};
use core::{
ffi::CStr,
ffi::{c_char, c_int, c_ulong, c_void},
ptr::{self, NonNull},
};
use std::ffi::CString;
pub(crate) struct PyClassTypeObject {
pub type_object: Py<PyType>,
pub is_immutable_type: bool,
#[expect(
dead_code,
reason = "this is just storage that must live as long as the type object"
)]
getset_defs: Vec<GetSetDefType>,
}
pub(crate) fn create_type_object<T>(py: Python<'_>) -> PyResult<PyClassTypeObject>
where
T: PyClass,
{
// Written this way to monomorphize the majority of the logic.
#[expect(clippy::too_many_arguments)]
unsafe fn inner(
py: Python<'_>,
base: *mut ffi::PyTypeObject,
dealloc: unsafe extern "C" fn(*mut ffi::PyObject),
dealloc_with_gc: unsafe extern "C" fn(*mut ffi::PyObject),
synthesized_traverse: ffi::traverseproc,
synthesized_clear: ffi::inquiry,
is_mapping: bool,
is_sequence: bool,
is_immutable_type: bool,
doc: &'static CStr,
dict_offset: Option<PyObjectOffset>,
weaklist_offset: Option<PyObjectOffset>,
is_basetype: bool,
items_iter: PyClassItemsIter,
name: &'static str,
module: Option<&'static str>,
basicsize: ffi::Py_ssize_t,
) -> PyResult<PyClassTypeObject> {
unsafe {
PyTypeBuilder {
slots: Vec::new(),
method_defs: Vec::new(),
member_defs: Vec::new(),
getset_builders: HashMap::new(),
#[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
cleanup: Vec::new(),
tp_base: base,
tp_dealloc: dealloc,
tp_dealloc_with_gc: dealloc_with_gc,
synthesized_traverse,
synthesized_clear,
is_mapping,
is_sequence,
is_immutable_type,
has_new: false,
has_dealloc: false,
has_getitem: false,
has_setitem: false,
has_traverse: false,
has_clear: false,
dict_offset: None,
class_flags: 0,
#[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
buffer_procs: Default::default(),
}
.type_doc(doc)
.offsets(dict_offset, weaklist_offset)
.set_is_basetype(is_basetype)
.class_items(items_iter)
.build(py, name, module, basicsize)
}
}
unsafe {
inner(
py,
T::BaseType::type_object_raw(py),
tp_dealloc::<T>,
tp_dealloc_with_gc::<T>,
synthesized_traverse::<T>,
synthesized_clear::<T>,
T::IS_MAPPING,
T::IS_SEQUENCE,
T::IS_IMMUTABLE_TYPE,
T::DOC,
T::dict_offset(),
T::weaklist_offset(),
T::IS_BASETYPE,
T::items_iter(),
<T as PyClass>::NAME,
<T as PyClassImpl>::MODULE,
<T as PyClassImpl>::Layout::BASIC_SIZE,
)
}
}
#[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
type PyTypeBuilderCleanup = Box<dyn Fn(&PyTypeBuilder, *mut ffi::PyTypeObject)>;
struct PyTypeBuilder {
slots: Vec<ffi::PyType_Slot>,
method_defs: Vec<ffi::PyMethodDef>,
member_defs: Vec<ffi::PyMemberDef>,
getset_builders: HashMap<&'static CStr, GetSetDefBuilder>,
/// Used to patch the type objects for the things there's no
/// PyType_FromSpec API for... there's no reason this should work,
/// except for that it does and we have tests.
#[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
cleanup: Vec<PyTypeBuilderCleanup>,
tp_base: *mut ffi::PyTypeObject,
tp_dealloc: ffi::destructor,
tp_dealloc_with_gc: ffi::destructor,
/// `tp_traverse` / `tp_clear` to install when the class needs the slot but defines no
/// `__traverse__` / `__clear__` of its own.
synthesized_traverse: ffi::traverseproc,
synthesized_clear: ffi::inquiry,
is_mapping: bool,
is_sequence: bool,
is_immutable_type: bool,
has_new: bool,
has_dealloc: bool,
has_getitem: bool,
has_setitem: bool,
has_traverse: bool,
has_clear: bool,
dict_offset: Option<PyObjectOffset>,
class_flags: c_ulong,
// Before Python 3.9, need to patch in buffer methods manually (they don't work in slots)
#[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
buffer_procs: ffi::PyBufferProcs,
}
impl PyTypeBuilder {
/// # Safety
/// The given pointer must be of the correct type for the given slot
unsafe fn push_slot<T>(&mut self, slot: c_int, pfunc: *mut T) {
match slot {
ffi::Py_tp_new => self.has_new = true,
ffi::Py_tp_dealloc => self.has_dealloc = true,
ffi::Py_mp_subscript => self.has_getitem = true,
ffi::Py_mp_ass_subscript => self.has_setitem = true,
ffi::Py_tp_traverse => {
self.has_traverse = true;
self.class_flags |= ffi::Py_TPFLAGS_HAVE_GC;
}
ffi::Py_tp_clear => self.has_clear = true,
#[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
ffi::Py_bf_getbuffer => {
// Safety: slot.pfunc is a valid function pointer
self.buffer_procs.bf_getbuffer =
Some(unsafe { core::mem::transmute::<*mut T, ffi::getbufferproc>(pfunc) });
}
#[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
ffi::Py_bf_releasebuffer => {
// Safety: slot.pfunc is a valid function pointer
self.buffer_procs.bf_releasebuffer =
Some(unsafe { core::mem::transmute::<*mut T, ffi::releasebufferproc>(pfunc) });
}
_ => {}
}
self.slots.push(ffi::PyType_Slot {
slot,
pfunc: pfunc as _,
});
}
/// # Safety
/// It is the caller's responsibility that `data` is of the correct type for the given slot.
unsafe fn push_raw_vec_slot<T>(&mut self, slot: c_int, mut data: Vec<T>) {
if !data.is_empty() {
// Python expects a zeroed entry to mark the end of the defs
unsafe {
data.push(core::mem::zeroed());
self.push_slot(slot, Box::into_raw(data.into_boxed_slice()) as *mut c_void);
}
}
}
fn pymethod_def(&mut self, def: &PyMethodDefType) {
match def {
PyMethodDefType::Getter(getter) => self
.getset_builders
.entry(getter.name)
.or_default()
.add_getter(getter),
PyMethodDefType::Setter(setter) => self
.getset_builders
.entry(setter.name)
.or_default()
.add_setter(setter),
PyMethodDefType::Deleter(deleter) => self
.getset_builders
.entry(deleter.name)
.or_default()
.add_deleter(deleter),
PyMethodDefType::Method(def) => self.method_defs.push(def.into_raw()),
// These class attributes are added after the type gets created by LazyStaticType
PyMethodDefType::ClassAttribute(_) => {}
PyMethodDefType::StructMember(def) => self.member_defs.push(*def),
}
}
fn finalize_methods_and_properties(&mut self) -> Vec<GetSetDefType> {
let method_defs: Vec<pyo3_ffi::PyMethodDef> = core::mem::take(&mut self.method_defs);
// Safety: Py_tp_methods expects a raw vec of PyMethodDef
unsafe { self.push_raw_vec_slot(ffi::Py_tp_methods, method_defs) };
let member_defs = core::mem::take(&mut self.member_defs);
// Safety: Py_tp_members expects a raw vec of PyMemberDef
unsafe { self.push_raw_vec_slot(ffi::Py_tp_members, member_defs) };
let mut getset_destructors = Vec::with_capacity(self.getset_builders.len());
#[allow(unused_mut, reason = "not modified on PyPy")]
let mut property_defs: Vec<_> = self
.getset_builders
.iter()
.map(|(name, builder)| {
let (def, destructor) = builder.as_get_set_def(name);
getset_destructors.push(destructor);
def
})
.collect();
// PyPy automatically adds __dict__ getter / setter.
#[cfg(not(PyPy))]
// Supported on unlimited API for all versions, and on 3.9+ for limited API
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
if let Some(dict_offset) = self.dict_offset {
let get_dict;
let closure;
// PyObject_GenericGetDict not in the limited API until Python 3.10.
#[cfg(any(not(Py_LIMITED_API), Py_3_10))]
{
let _ = dict_offset;
get_dict = ffi::PyObject_GenericGetDict;
closure = ptr::null_mut();
}
// ... so we write a basic implementation ourselves
#[cfg(not(any(not(Py_LIMITED_API), Py_3_10)))]
{
extern "C" fn get_dict_impl(
object: *mut ffi::PyObject,
closure: *mut c_void,
) -> *mut ffi::PyObject {
unsafe {
trampoline(|_| {
let dict_offset = closure as ffi::Py_ssize_t;
// we don't support negative dict_offset here; PyO3 doesn't set it negative
assert!(dict_offset > 0);
let dict_ptr =
object.byte_offset(dict_offset).cast::<*mut ffi::PyObject>();
if (*dict_ptr).is_null() {
core::ptr::write(dict_ptr, ffi::PyDict_New());
}
Ok(ffi::compat::Py_XNewRef(*dict_ptr))
})
}
}
get_dict = get_dict_impl;
let PyObjectOffset::Absolute(offset) = dict_offset;
closure = offset as _;
}
property_defs.push(ffi::PyGetSetDef {
name: c"__dict__".as_ptr(),
get: Some(get_dict),
set: Some(ffi::PyObject_GenericSetDict),
doc: ptr::null(),
closure,
});
}
// Safety: Py_tp_getset expects a raw vec of PyGetSetDef
unsafe { self.push_raw_vec_slot(ffi::Py_tp_getset, property_defs) };
// If mapping methods implemented, define sequence methods get implemented too.
// CPython does the same for Python `class` statements.
// NB we don't implement sq_length to avoid annoying CPython behaviour of automatically adding
// the length to negative indices.
// Don't add these methods for "pure" mappings.
if !self.is_mapping && self.has_getitem {
// Safety: This is the correct slot type for Py_sq_item
unsafe {
self.push_slot(
ffi::Py_sq_item,
get_sequence_item_from_mapping as *mut c_void,
)
}
}
if !self.is_mapping && self.has_setitem {
// Safety: This is the correct slot type for Py_sq_ass_item
unsafe {
self.push_slot(
ffi::Py_sq_ass_item,
assign_sequence_item_from_mapping as *mut c_void,
)
}
}
getset_destructors
}
fn set_is_basetype(mut self, is_basetype: bool) -> Self {
if is_basetype {
self.class_flags |= ffi::Py_TPFLAGS_BASETYPE;
}
self
}
/// # Safety
/// All slots in the PyClassItemsIter should be correct
unsafe fn class_items(mut self, iter: PyClassItemsIter) -> Self {
for items in iter {
for slot in items.slots {
unsafe { self.push_slot(slot.slot, slot.pfunc) };
}
for method in items.methods {
self.pymethod_def(method);
}
}
self
}
fn type_doc(mut self, type_doc: &'static CStr) -> Self {
if !type_doc.is_empty() {
unsafe { self.push_slot(ffi::Py_tp_doc, type_doc.as_ptr() as *mut c_char) }
#[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
{
// Until CPython 3.10, tp_doc was treated specially for
// heap-types, and it removed the text_signature value from it.
// We go in after the fact and replace tp_doc with something
// that _does_ include the text_signature value!
self.cleanup
.push(Box::new(move |_self, type_object| unsafe {
ffi::PyObject_Free((*type_object).tp_doc as _);
let slice = type_doc.to_bytes_with_nul();
let data = ffi::PyMem_Malloc(slice.len());
data.copy_from(slice.as_ptr() as _, slice.len());
(*type_object).tp_doc = data as _;
}))
}
}
self
}
fn offsets(
mut self,
dict_offset: Option<PyObjectOffset>,
#[allow(unused_variables)] weaklist_offset: Option<PyObjectOffset>,
) -> Self {
self.dict_offset = dict_offset;
#[cfg(Py_3_9)]
{
#[inline(always)]
fn offset_def(name: &'static CStr, offset: PyObjectOffset) -> ffi::PyMemberDef {
let (offset, flags) = match offset {
PyObjectOffset::Absolute(offset) => (offset, ffi::Py_READONLY),
#[cfg(Py_3_12)]
PyObjectOffset::Relative(offset) => {
(offset, ffi::Py_READONLY | ffi::Py_RELATIVE_OFFSET)
}
};
ffi::PyMemberDef {
name: name.as_ptr().cast(),
type_code: ffi::Py_T_PYSSIZET,
offset,
flags,
doc: core::ptr::null_mut(),
}
}
// __dict__ support
if let Some(dict_offset) = dict_offset {
self.member_defs
.push(offset_def(c"__dictoffset__", dict_offset));
}
// weakref support
if let Some(weaklist_offset) = weaklist_offset {
self.member_defs
.push(offset_def(c"__weaklistoffset__", weaklist_offset));
}
}
// Setting buffer protocols, tp_dictoffset and tp_weaklistoffset via slots doesn't work until
// Python 3.9, so on older versions we must manually fixup the type object.
#[cfg(all(not(Py_LIMITED_API), not(Py_3_9)))]
{
self.cleanup
.push(Box::new(move |builder, type_object| unsafe {
(*(*type_object).tp_as_buffer).bf_getbuffer = builder.buffer_procs.bf_getbuffer;
(*(*type_object).tp_as_buffer).bf_releasebuffer =
builder.buffer_procs.bf_releasebuffer;
match dict_offset {
Some(PyObjectOffset::Absolute(offset)) => {
(*type_object).tp_dictoffset = offset;
}
None => {}
}
match weaklist_offset {
Some(PyObjectOffset::Absolute(offset)) => {
(*type_object).tp_weaklistoffset = offset;
}
None => {}
}
}));
}
self
}
fn build(
mut self,
py: Python<'_>,
name: &'static str,
module_name: Option<&'static str>,
basicsize: ffi::Py_ssize_t,
) -> PyResult<PyClassTypeObject> {
// `c_ulong` and `c_uint` have the same size
// on some platforms (like windows)
#![allow(clippy::useless_conversion)]
let getset_defs = self.finalize_methods_and_properties();
unsafe { self.push_slot(ffi::Py_tp_base, self.tp_base) }
if !self.has_new {
#[cfg(not(Py_3_10))]
{
// Safety: This is the correct slot type for Py_tp_new
unsafe { self.push_slot(ffi::Py_tp_new, no_constructor_defined as *mut c_void) }
}
#[cfg(Py_3_10)]
{
self.class_flags |= ffi::Py_TPFLAGS_DISALLOW_INSTANTIATION;
}
}
// A reference cycle can run through the instance `__dict__` (`obj.x = obj`), so a class
// with a `__dict__` must be a GC type. `_call_traverse` / `_call_clear` service the
// `__dict__` when the user defines `__traverse__` / `__clear__`; synthesize whichever
// slot they did not.
//
// Must run before the `tp_dealloc` selection below, which keys off `has_traverse`.
if self.dict_offset.is_some() {
if !self.has_traverse {
let synthesized_traverse = self.synthesized_traverse;
// Safety: This is the correct slot type for Py_tp_traverse
unsafe { self.push_slot(ffi::Py_tp_traverse, synthesized_traverse as *mut c_void) }
}
if !self.has_clear {
let synthesized_clear = self.synthesized_clear;
// Safety: This is the correct slot type for Py_tp_clear
unsafe { self.push_slot(ffi::Py_tp_clear, synthesized_clear as *mut c_void) }
}
}
let base_is_gc = unsafe { ffi::PyType_IS_GC(self.tp_base) == 1 };
let tp_dealloc = if self.has_traverse || base_is_gc {
self.tp_dealloc_with_gc
} else {
self.tp_dealloc
};
unsafe { self.push_slot(ffi::Py_tp_dealloc, tp_dealloc as *mut c_void) }
if self.has_clear && !self.has_traverse {
return Err(PyTypeError::new_err(format!(
"`#[pyclass]` {name} implements __clear__ without __traverse__"
)));
}
// If this type is a GC type, and the base also is, we may need to add
// `tp_traverse` / `tp_clear` implementations to call the base, if this type didn't
// define `__traverse__` or `__clear__`.
//
// This is because when Py_TPFLAGS_HAVE_GC is set, then `tp_traverse` and
// `tp_clear` are not inherited.
if ((self.class_flags & ffi::Py_TPFLAGS_HAVE_GC) != 0) && base_is_gc {
// If this assertion breaks, need to consider doing the same for __traverse__.
assert!(self.has_traverse); // Py_TPFLAGS_HAVE_GC is set when a `__traverse__` method is found
if !self.has_clear {
let synthesized_clear = self.synthesized_clear;
// Safety: This is the correct slot type for Py_tp_clear
unsafe { self.push_slot(ffi::Py_tp_clear, synthesized_clear as *mut c_void) }
}
}
// For sequences, implement sq_length instead of mp_length
if self.is_sequence {
for slot in &mut self.slots {
if slot.slot == ffi::Py_mp_length {
slot.slot = ffi::Py_sq_length;
}
}
}
// Add empty sentinel at the end
// Safety: python expects this empty slot
unsafe { self.push_slot(0, ptr::null_mut::<c_void>()) }
let class_name = py_class_qualified_name(module_name, name)?;
let flags = ffi::Py_TPFLAGS_DEFAULT | self.class_flags;
#[cfg(Py_3_15)]
let type_object = {
let mut slots = [
ffi::PySlot_DATA(ffi::Py_tp_name, class_name.as_ptr() as *mut c_void),
ffi::PySlot_UINT64(ffi::Py_tp_flags, flags.into()),
ffi::PySlot_DATA(ffi::Py_tp_slots, self.slots.as_mut_ptr().cast::<c_void>()),
match basicsize {
1.. => ffi::PySlot_SIZE(ffi::Py_tp_basicsize, basicsize),
// zero size; don't set the slot at all, the VM will use the parent size
//
// This can *ONLY* be hit on the variable-sized case with a rust ZST,
// as a fully-sized case will always have size at least equal to `PyObject`
0 => ffi::PySlot_END(),
..0 => ffi::PySlot_SIZE(ffi::Py_tp_extra_basicsize, -basicsize),
},
// NB: insert additional slots BEFORE `basicsize` slot as it might be null
ffi::PySlot_END(),
];
// SAFETY: We've correctly setup the slots array at this point.
// The FFI call is known to return a new type object or null on error.
unsafe {
ffi::PyType_FromSlots(slots.as_mut_ptr())
.assume_owned_or_err(py)?
.cast_into_unchecked::<PyType>()
}
};
#[cfg(not(Py_3_15))]
let type_object = {
let mut spec = ffi::PyType_Spec {
name: class_name.as_ptr() as _,
basicsize: basicsize as c_int,
itemsize: 0,
flags: flags.try_into().unwrap(),
slots: self.slots.as_mut_ptr(),
};
// SAFETY: We've correctly setup the PyType_Spec at this point.
// The FFI call is known to return a new type object or null on error.
unsafe {
ffi::PyType_FromSpec(&mut spec)
.assume_owned_or_err(py)?
.cast_into_unchecked::<PyType>()
}
};
#[cfg(not(Py_3_11))]
bpo_45315_workaround(py, class_name);
#[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
for cleanup in core::mem::take(&mut self.cleanup) {
cleanup(&self, type_object.as_type_ptr());
}
Ok(PyClassTypeObject {
type_object: type_object.unbind(),
is_immutable_type: self.is_immutable_type,
getset_defs,
})
}
}
fn py_class_qualified_name(module_name: Option<&str>, class_name: &str) -> PyResult<CString> {
Ok(CString::new(format!(
"{}.{}",
module_name.unwrap_or("builtins"),
class_name
))?)
}
/// Workaround for Python issue 45315; no longer necessary in Python 3.11
#[inline]
#[cfg(not(Py_3_11))]
fn bpo_45315_workaround(py: Python<'_>, class_name: CString) {
#[cfg(Py_LIMITED_API)]
{
// Must check version at runtime for abi3 wheels - they could run against a higher version
// than the build config suggests.
use crate::sync::PyOnceLock;
static IS_PYTHON_3_11: PyOnceLock<bool> = PyOnceLock::new();
if *IS_PYTHON_3_11.get_or_init(py, || py.version_info() >= (3, 11)) {
// No fix needed - the wheel is running on a sufficiently new interpreter.
return;
}
}
#[cfg(not(Py_LIMITED_API))]
{
// suppress unused variable warning
let _ = py;
}
core::mem::forget(class_name);
}
/// Default new implementation
#[cfg(not(Py_3_10))]
unsafe extern "C" fn no_constructor_defined(
subtype: *mut ffi::PyTypeObject,
_args: *mut ffi::PyObject,
_kwds: *mut ffi::PyObject,
) -> *mut ffi::PyObject {
unsafe {
trampoline(|py| {
let tpobj = PyType::from_borrowed_type_ptr(py, subtype);
// unlike `fully_qualified_name`, this always include the module
let module = tpobj
.module()
.map_or_else(|_| "<unknown>".into(), |s| s.to_string());
let qualname = tpobj.qualname();
let qualname = qualname.map_or_else(|_| "<unknown>".into(), |s| s.to_string());
Err(crate::exceptions::PyTypeError::new_err(format!(
"cannot create '{module}.{qualname}' instances"
)))
})
}
}
#[derive(Default)]
struct GetSetDefBuilder {
doc: Option<&'static CStr>,
getter: Option<Getter>,
setter: Option<Setter>,
deleter: Option<Deleter>,
}
impl GetSetDefBuilder {
fn add_getter(&mut self, getter: &PyGetterDef) {
// TODO: be smarter about merging getter and setter docs
if self.doc.is_none() {
self.doc = getter.doc;
}
// TODO: return an error if getter already defined?
self.getter = Some(getter.meth)
}
fn add_setter(&mut self, setter: &PySetterDef) {
// TODO: be smarter about merging getter and setter docs
if self.doc.is_none() {
self.doc = setter.doc;
}
// TODO: return an error if setter already defined?
self.setter = Some(setter.meth)
}
fn add_deleter(&mut self, deleter: &PyDeleterDef) {
// TODO: be smarter about merging getter, setter and deleter docs
if self.doc.is_none() {
self.doc = deleter.doc;
}
// TODO: return an error if deleter already defined?
self.deleter = Some(deleter.meth)
}
fn as_get_set_def(&self, name: &'static CStr) -> (ffi::PyGetSetDef, GetSetDefType) {
let getset_type = match (self.getter, self.setter, self.deleter) {
(None, None, None) => {
unreachable!("GetSetDefBuilder expected to always have either getter or setter")
}
(Some(getter), None, None) => GetSetDefType::Getter(getter),
(None, Some(setter), None) => GetSetDefType::Setter(setter),
(getter, setter, deleter) => {
GetSetDefType::Combination(Box::new(GetSetDeleteCombination {
getter,
setter,
deleter,
}))
}
};
let getset_def = getset_type.create_py_get_set_def(name, self.doc);
(getset_def, getset_type)
}
}
/// Possible forms of property - either a getter, setter, or both
enum GetSetDefType {
Getter(Getter),
Setter(Setter),
// The box is here so that the `GetSetDeleteCombination` has a stable
// memory address even if the `GetSetDeleteCombination` enum is moved
Combination(Box<GetSetDeleteCombination>),
}
pub(crate) struct GetSetDeleteCombination {
getter: Option<Getter>,
setter: Option<Setter>,
deleter: Option<Deleter>,
}
impl GetSetDefType {
/// Fills a PyGetSetDef structure
/// It is only valid for as long as this GetSetDefType remains alive,
/// as well as name and doc members
pub(crate) fn create_py_get_set_def(
&self,
name: &CStr,
doc: Option<&CStr>,
) -> ffi::PyGetSetDef {
let (get, set, closure): (Option<ffi::getter>, Option<ffi::setter>, *mut c_void) =
match self {
&Self::Getter(closure) => {
unsafe extern "C" fn getter(
slf: *mut ffi::PyObject,
closure: *mut c_void,
) -> *mut ffi::PyObject {
let slf = unsafe { NonNull::new_unchecked(slf) };
// Safety: PyO3 sets the closure when constructing the ffi getter so this cast should always be valid
let getter: Getter = unsafe { core::mem::transmute(closure) };
unsafe { trampoline(|py| getter(py, slf)) }
}
(Some(getter), None, closure as Getter as _)
}
&Self::Setter(closure) => {
unsafe extern "C" fn setter(
slf: *mut ffi::PyObject,
value: *mut ffi::PyObject,
closure: *mut c_void,
) -> c_int {
let slf = unsafe { NonNull::new_unchecked(slf) };
// Safety: PyO3 sets the closure when constructing the ffi setter so this cast should always be valid
let setter: Setter = unsafe { core::mem::transmute(closure) };
unsafe {
trampoline(|py| {
if let Some(value) = NonNull::new(value) {
setter(py, slf, value)
} else {
Err(PyAttributeError::new_err("property has no deleter"))
}
})
}
}
(None, Some(setter), closure as Setter as _)
}
Self::Combination(closure) => {
unsafe extern "C" fn getset_getter(
slf: *mut ffi::PyObject,
closure: *mut c_void,
) -> *mut ffi::PyObject {
let slf = unsafe { NonNull::new_unchecked(slf) };
let getset: &GetSetDeleteCombination = unsafe { &*closure.cast() };
// we only call this method if getter is set
unsafe { trampoline(|py| getset.getter.unwrap_unchecked()(py, slf)) }
}
unsafe extern "C" fn getset_setter(
slf: *mut ffi::PyObject,
value: *mut ffi::PyObject,
closure: *mut c_void,
) -> c_int {
let slf = unsafe { NonNull::new_unchecked(slf) };
let getset: &GetSetDeleteCombination = unsafe { &*closure.cast() };
unsafe {
trampoline(|py| {
if let Some(value) = NonNull::new(value) {
getset.setter.ok_or_else(|| {
PyAttributeError::new_err("property has no setter")
})?(py, slf, value)
} else {
getset.deleter.ok_or_else(|| {
PyAttributeError::new_err("property has no deleter")
})?(py, slf)
}
})
}
}
(
closure.getter.is_some().then_some(getset_getter),
Some(getset_setter),
NonNull::<GetSetDeleteCombination>::from(closure.as_ref())
.cast()
.as_ptr(),
)
}
};
ffi::PyGetSetDef {
name: name.as_ptr(),
doc: doc.map_or(ptr::null(), CStr::as_ptr),
get,
set,
closure,
}
}
}
+64
View File
@@ -0,0 +1,64 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
use core::{
ffi::{c_int, c_void},
marker::PhantomData,
num::NonZero,
};
use crate::{ffi, Py};
/// Error returned by a `__traverse__` visitor implementation.
#[repr(transparent)]
pub struct PyTraverseError(NonZero<c_int>);
impl PyTraverseError {
/// Returns the error code.
pub(crate) fn into_inner(self) -> c_int {
self.0.into()
}
}
/// Object visitor for GC.
#[derive(Clone)]
pub struct PyVisit<'a> {
pub(crate) visit: ffi::visitproc,
pub(crate) arg: *mut c_void,
/// Prevents the `PyVisit` from outliving the `__traverse__` call.
pub(crate) _guard: PhantomData<&'a ()>,
}
impl PyVisit<'_> {
/// Visit `obj`.
///
/// Note: `obj` accepts a variety of types, including
/// - `&Py<T>`
/// - `&Option<Py<T>>`
/// - `Option<&Py<T>>`
pub fn call<'a, T, U: 'a>(&self, obj: T) -> Result<(), PyTraverseError>
where
T: Into<Option<&'a Py<U>>>,
{
let ptr = obj.into().map_or_else(core::ptr::null_mut, Py::as_ptr);
if !ptr.is_null() {
match NonZero::new(unsafe { (self.visit)(ptr, self.arg) }) {
None => Ok(()),
Some(r) => Err(PyTraverseError(r)),
}
} else {
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::PyVisit;
use static_assertions::assert_not_impl_any;
#[test]
fn py_visit_not_send_sync() {
assert_not_impl_any!(PyVisit<'_>: Send, Sync);
}
}
+1211
View File
File diff suppressed because it is too large. Load diff
+262
View File
@@ -0,0 +1,262 @@
// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]
//! Contains initialization utilities for `#[pyclass]`.
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::impl_::pyclass::{PyClassBaseType, PyClassImpl};
use crate::impl_::pyclass_init::PyNativeTypeInitializer;
use crate::internal::pyclass_init::PyObjectInit;
use crate::pycell::impl_::PyClassObjectLayout;
use crate::{ffi, Bound, PyClass, PyResult, Python};
use crate::{ffi::PyTypeObject, pycell::impl_::PyClassObjectContents};
use core::marker::PhantomData;
/// Initializer for our `#[pyclass]` system.
///
/// You can use this type to initialize complicatedly nested `#[pyclass]`.
///
/// # Examples
///
/// ```
/// # use pyo3::prelude::*;
/// # use pyo3::py_run;
/// #[pyclass(subclass)]
/// struct BaseClass {
/// #[pyo3(get)]
/// basename: &'static str,
/// }
/// #[pyclass(extends=BaseClass, subclass)]
/// struct SubClass {
/// #[pyo3(get)]
/// subname: &'static str,
/// }
/// #[pyclass(extends=SubClass)]
/// struct SubSubClass {
/// #[pyo3(get)]
/// subsubname: &'static str,
/// }
///
/// #[pymethods]
/// impl SubSubClass {
/// #[new]
/// fn new() -> PyClassInitializer<Self> {
/// PyClassInitializer::from(BaseClass { basename: "base" })
/// .add_subclass(SubClass { subname: "sub" })
/// .add_subclass(SubSubClass {
/// subsubname: "subsub",
/// })
/// }
/// }
/// Python::attach(|py| {
/// let typeobj = py.get_type::<SubSubClass>();
/// let sub_sub_class = typeobj.call((), None).unwrap();
/// py_run!(
/// py,
/// sub_sub_class,
/// r#"
/// assert sub_sub_class.basename == 'base'
/// assert sub_sub_class.subname == 'sub'
/// assert sub_sub_class.subsubname == 'subsub'"#
/// );
/// });
/// ```
pub struct PyClassInitializer<T: PyClass> {
init: T,
super_init: <T::BaseType as PyClassBaseType>::Initializer,
}
impl<T: PyClass> PyClassInitializer<T> {
/// Constructs a new initializer from value `T` and base class' initializer.
///
/// It is recommended to use `add_subclass` instead of this method for most usage.
#[track_caller]
#[inline]
pub fn new(init: T, super_init: <T::BaseType as PyClassBaseType>::Initializer) -> Self {
Self { init, super_init }
}
/// Constructs a new initializer from an initializer for the base class.
///
/// # Examples
/// ```
/// use pyo3::prelude::*;
///
/// #[pyclass(subclass)]
/// struct BaseClass {
/// #[pyo3(get)]
/// value: i32,
/// }
///
/// impl BaseClass {
/// fn new(value: i32) -> PyResult<Self> {
/// Ok(Self { value })
/// }
/// }
///
/// #[pyclass(extends=BaseClass)]
/// struct SubClass {}
///
/// #[pymethods]
/// impl SubClass {
/// #[new]
/// fn new(value: i32) -> PyResult<PyClassInitializer<Self>> {
/// let base_init = PyClassInitializer::from(BaseClass::new(value)?);
/// Ok(base_init.add_subclass(SubClass {}))
/// }
/// }
///
/// fn main() -> PyResult<()> {
/// Python::attach(|py| {
/// let m = PyModule::new(py, "example")?;
/// m.add_class::<SubClass>()?;
/// m.add_class::<BaseClass>()?;
///
/// let instance = m.getattr("SubClass")?.call1((92,))?;
///
/// // `SubClass` does not have a `value` attribute, but `BaseClass` does.
/// let n = instance.getattr("value")?.extract::<i32>()?;
/// assert_eq!(n, 92);
///
/// Ok(())
/// })
/// }
/// ```
#[track_caller]
#[inline]
pub fn add_subclass<S>(self, subclass_value: S) -> PyClassInitializer<S>
where
T: PyClassBaseType<Initializer = Self>,
S: PyClass<BaseType = T>,
{
PyClassInitializer::new(subclass_value, self)
}
/// Creates a new class object and initializes it.
pub(crate) fn create_class_object(self, py: Python<'_>) -> PyResult<Bound<'_, T>>
where
T: PyClass,
{
unsafe { self.create_class_object_of_type(py, T::type_object_raw(py)) }
}
/// Creates a new class object and initializes it given a typeobject `subtype`.
///
/// # Safety
/// `subtype` must be a valid pointer to the type object of T or a subclass.
pub(crate) unsafe fn create_class_object_of_type(
self,
py: Python<'_>,
target_type: *mut crate::ffi::PyTypeObject,
) -> PyResult<Bound<'_, T>>
where
T: PyClass,
{
let obj = unsafe { self.super_init.into_new_object(py, target_type)? };
// SAFETY: `obj` is constructed using `T::Layout` but has not been initialized yet
let contents = unsafe { <T as PyClassImpl>::Layout::contents_uninit(obj) };
let new_contents = PyClassObjectContents::new(self.init);
// CPython 3.11 and 3.12 eagerly create the instance dict for types with a nonzero
// `tp_dictoffset` in `_PyObject_InitializeDict`, storing an owned reference in
// the `__dict__` slot, which lives inside `contents`. Carry that value over
// instead of clobbering it below, otherwise it leaks. Python 3.13 returned to
// creating the instance dict lazily
//
// The condition is `not(Py_3_13)` rather than `all(Py_3_11, not(Py_3_13))`
// because an abi3 build with a lower minimum version can still run on 3.11 and
// 3.12; on 3.10 and older this is a harmless no-op (the slot is always null
// there).
#[cfg(not(Py_3_13))]
let new_contents = {
let mut new_contents = new_contents;
if eagerly_created_dict_possible::<T>(py) {
// SAFETY: `tp_alloc` zero-initializes the object, so the slot contains either
// zeroes (a valid empty slot value) or a valid owned pointer stored by the base
// `tp_new` through the type's `tp_dictoffset`.
unsafe {
let contents_ptr = (*contents).as_mut_ptr();
let dict_ptr = &raw const (*contents_ptr).dict;
new_contents.dict = core::ptr::read(dict_ptr);
}
}
new_contents
};
// SAFETY: `contents` is a non-null pointer to the space allocated for our
// `PyClassObjectContents` (either statically in Rust or dynamically by Python)
unsafe { (*contents).write(new_contents) };
// Safety: obj is a valid pointer to an object of type `target_type`, which` is a known
// subclass of `T`
Ok(unsafe { obj.assume_owned(py).cast_into_unchecked() })
}
}
/// Whether the running interpreter may have eagerly created an instance dict for `T`
/// during `tp_new` (CPython 3.11 and 3.12 only).
///
/// For native builds the compile-time `not(Py_3_13)` gate at the call site is exact; abi3
/// builds with a minimum version below 3.13 must check the interpreter version at runtime
#[cfg(not(Py_3_13))]
#[inline]
fn eagerly_created_dict_possible<T: PyClassImpl>(py: Python<'_>) -> bool {
if core::mem::size_of::<T::Dict>() == 0 {
return false;
}
cfg_select! {
Py_LIMITED_API =>
{
use crate::sync::PyOnceLock;
static IS_PYTHON_3_11_OR_3_12: PyOnceLock<bool> = PyOnceLock::new();
*IS_PYTHON_3_11_OR_3_12.get_or_init(py, || {
let version_info = py.version_info();
matches!((version_info.major, version_info.minor), (3, 11) | (3, 12))
})
}
not(Py_LIMITED_API) =>
{
let _ = py;
cfg!(Py_3_11)
}
}
}
impl<T: PyClass> PyObjectInit<T> for PyClassInitializer<T> {
unsafe fn into_new_object(
self,
py: Python<'_>,
subtype: *mut PyTypeObject,
) -> PyResult<*mut ffi::PyObject> {
unsafe {
self.create_class_object_of_type(py, subtype)
.map(Bound::into_ptr)
}
}
}
impl<T> From<T> for PyClassInitializer<T>
where
T: PyClass,
T::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<T::BaseType>>,
{
#[inline]
fn from(value: T) -> PyClassInitializer<T> {
Self::new(value, PyNativeTypeInitializer(PhantomData))
}
}
impl<S, B> From<(S, B)> for PyClassInitializer<S>
where
S: PyClass<BaseType = B>,
B: PyClass + PyClassBaseType<Initializer = PyClassInitializer<B>>,
B::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<B::BaseType>>,
{
#[track_caller]
#[inline]
fn from(sub_and_base: (S, B)) -> PyClassInitializer<S> {
let (sub, base) = sub_and_base;
PyClassInitializer::from(base).add_subclass(sub)
}
}
Loaded 100 of 1714 files, more files were not shown because too many files have changed in this diff. Show more