Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add support for type hinting objects #4917

Merged
merged 11 commits into from
Feb 19, 2025
2 changes: 2 additions & 0 deletions newsfragments/4917.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added support for creating [types.GenericAlias](https://docs.python.org/3/library/types.html#types.GenericAlias)
objects in PyO3 with `pyo3::types::PyGenericAlias`.
12 changes: 12 additions & 0 deletions pyo3-ffi/src/genericaliasobject.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#[cfg(Py_3_9)]
use crate::object::{PyObject, PyTypeObject};

#[cfg_attr(windows, link(name = "pythonXY"))]
extern "C" {
#[cfg(Py_3_9)]
#[cfg_attr(PyPy, link_name = "PyPy_GenericAlias")]
pub fn Py_GenericAlias(origin: *mut PyObject, args: *mut PyObject) -> *mut PyObject;

#[cfg(Py_3_9)]
pub static mut Py_GenericAliasType: PyTypeObject;
}
4 changes: 3 additions & 1 deletion pyo3-ffi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -410,6 +410,8 @@ pub use self::enumobject::*;
pub use self::fileobject::*;
pub use self::fileutils::*;
pub use self::floatobject::*;
#[cfg(Py_3_9)]
pub use self::genericaliasobject::*;
pub use self::import::*;
pub use self::intrcheck::*;
pub use self::iterobject::*;
@@ -479,7 +481,7 @@ mod fileobject;
mod fileutils;
mod floatobject;
// skipped empty frameobject.h
// skipped genericaliasobject.h
mod genericaliasobject;
mod import;
// skipped interpreteridobject.h
mod intrcheck;
72 changes: 72 additions & 0 deletions src/types/genericalias.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use crate::err::PyResult;
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::py_result_ext::PyResultExt;
use crate::{ffi, Bound, PyAny, Python};

/// Represents a Python [`types.GenericAlias`](https://docs.python.org/3/library/types.html#types.GenericAlias) object.
///
/// Values of this type are accessed via PyO3's smart pointers, e.g. as
/// [`Py<PyGenericAlias>`][crate::Py] or [`Bound<'py, PyGenericAlias>`][Bound].
///
/// This type is particularly convenient for users implementing
/// [`__class_getitem__`](https://docs.python.org/3/reference/datamodel.html#object.__class_getitem__)
/// for PyO3 classes to allow runtime parameterization.
#[repr(transparent)]
pub struct PyGenericAlias(PyAny);

pyobject_native_type!(
PyGenericAlias,
ffi::PyDictObject,
pyobject_native_static_type_object!(ffi::Py_GenericAliasType)
);

impl PyGenericAlias {
/// Creates a new Python GenericAlias object.
///
/// origin should be a non-parameterized generic class.
/// args should be a tuple (possibly of length 1) of types which parameterize origin.
pub fn new<'py>(
py: Python<'py>,
origin: &Bound<'py, PyAny>,
args: &Bound<'py, PyAny>,
) -> PyResult<Bound<'py, PyGenericAlias>> {
unsafe {
ffi::Py_GenericAlias(origin.as_ptr(), args.as_ptr())
.assume_owned_or_err(py)
.downcast_into_unchecked()
}
}
}

#[cfg(test)]
mod tests {
use crate::instance::BoundObject;
use crate::types::any::PyAnyMethods;
use crate::{ffi, Python};

use super::PyGenericAlias;

// Tests that PyGenericAlias::new is identical to types.GenericAlias
// created from Python.
#[test]
fn equivalency_test() {
Python::with_gil(|py| {
let list_int = py
.eval(ffi::c_str!("list[int]"), None, None)
.unwrap()
.into_bound();

let cls = py
.eval(ffi::c_str!("list"), None, None)
.unwrap()
.into_bound();
let key = py
.eval(ffi::c_str!("(int,)"), None, None)
.unwrap()
.into_bound();
let generic_alias = PyGenericAlias::new(py, &cls, &key).unwrap();

assert!(generic_alias.eq(list_int).unwrap());
})
}
}
4 changes: 4 additions & 0 deletions src/types/mod.rs
Original file line number Diff line number Diff line change
@@ -25,6 +25,8 @@ pub use self::frozenset::{PyFrozenSet, PyFrozenSetBuilder, PyFrozenSetMethods};
pub use self::function::PyCFunction;
#[cfg(all(not(Py_LIMITED_API), not(all(PyPy, not(Py_3_8)))))]
pub use self::function::PyFunction;
#[cfg(Py_3_9)]
pub use self::genericalias::PyGenericAlias;
pub use self::iterator::PyIterator;
pub use self::list::{PyList, PyListMethods};
pub use self::mapping::{PyMapping, PyMappingMethods};
@@ -248,6 +250,8 @@ pub(crate) mod float;
mod frame;
pub(crate) mod frozenset;
mod function;
#[cfg(Py_3_9)]
pub(crate) mod genericalias;
pub(crate) mod iterator;
pub(crate) mod list;
pub(crate) mod mapping;
Loading