Merge pull request #3703 from davidhewitt/module2

implement `PyModuleMethods`
This commit is contained in:
David Hewitt 2023-12-29 22:30:08 +00:00 committed by GitHub
commit e1fcb4efce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 341 additions and 58 deletions

View File

@ -34,6 +34,7 @@ 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::module::PyModuleMethods;
pub use crate::types::sequence::PySequenceMethods;
pub use crate::types::set::PySetMethods;
pub use crate::types::string::PyStringMethods;

View File

@ -1,4 +1,4 @@
use crate::{types::any::PyAnyMethods, Bound, PyAny, PyResult};
use crate::{types::any::PyAnyMethods, Bound, PyAny, PyResult, PyTypeCheck};
mod sealed {
use super::*;
@ -11,10 +11,16 @@ mod sealed {
use sealed::Sealed;
pub(crate) trait PyResultExt<'py>: Sealed {
fn downcast_into<T: PyTypeCheck>(self) -> PyResult<Bound<'py, T>>;
unsafe fn downcast_into_unchecked<T>(self) -> PyResult<Bound<'py, T>>;
}
impl<'py> PyResultExt<'py> for PyResult<Bound<'py, PyAny>> {
#[inline]
fn downcast_into<T: PyTypeCheck>(self) -> PyResult<Bound<'py, T>> where {
self.and_then(|instance| instance.downcast_into().map_err(Into::into))
}
#[inline]
unsafe fn downcast_into_unchecked<T>(self) -> PyResult<Bound<'py, T>> {
self.map(|instance| instance.downcast_into_unchecked())

View File

@ -294,7 +294,7 @@ mod iterator;
pub(crate) mod list;
pub(crate) mod mapping;
mod memoryview;
mod module;
pub(crate) mod module;
mod none;
mod notimplemented;
mod num;

View File

@ -1,11 +1,12 @@
use crate::callback::IntoPyCallbackOutput;
use crate::err::{PyErr, PyResult};
use crate::exceptions;
use crate::ffi;
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::pyclass::PyClass;
use crate::types::{PyAny, PyCFunction, PyDict, PyList, PyString};
use crate::{IntoPy, Py, PyObject, Python};
use std::ffi::{CStr, CString};
use crate::types::{
any::PyAnyMethods, list::PyListMethods, PyAny, PyCFunction, PyDict, PyList, PyString,
};
use crate::{exceptions, ffi, Bound, IntoPy, Py, PyNativeType, PyObject, Python};
use std::ffi::CString;
use std::str;
/// Represents a Python [`module`][1] object.
@ -151,11 +152,7 @@ impl PyModule {
/// Returns the module's `__dict__` attribute, which contains the module's symbol table.
pub fn dict(&self) -> &PyDict {
unsafe {
// PyModule_GetDict returns borrowed ptr; must make owned for safety (see #890).
let ptr = ffi::PyModule_GetDict(self.as_ptr());
self.py().from_owned_ptr(ffi::_Py_NewRef(ptr))
}
self.as_borrowed().dict().into_gil_ref()
}
/// Returns the index (the `__all__` attribute) of the module,
@ -163,34 +160,14 @@ impl PyModule {
///
/// `__all__` declares the items that will be imported with `from my_module import *`.
pub fn index(&self) -> PyResult<&PyList> {
let __all__ = __all__(self.py());
match self.getattr(__all__) {
Ok(idx) => idx.downcast().map_err(PyErr::from),
Err(err) => {
if err.is_instance_of::<exceptions::PyAttributeError>(self.py()) {
let l = PyList::empty(self.py());
self.setattr(__all__, l).map_err(PyErr::from)?;
Ok(l)
} else {
Err(err)
}
}
}
self.as_borrowed().index().map(Bound::into_gil_ref)
}
/// Returns the name (the `__name__` attribute) of the module.
///
/// May fail if the module does not have a `__name__` attribute.
pub fn name(&self) -> PyResult<&str> {
let ptr = unsafe { ffi::PyModule_GetName(self.as_ptr()) };
if ptr.is_null() {
Err(PyErr::fetch(self.py()))
} else {
let name = unsafe { CStr::from_ptr(ptr) }
.to_str()
.expect("PyModule_GetName expected to return utf8");
Ok(name)
}
self.as_borrowed().name()?.into_gil_ref().to_str()
}
/// Returns the filename (the `__file__` attribute) of the module.
@ -198,11 +175,7 @@ impl PyModule {
/// May fail if the module does not have a `__file__` attribute.
#[cfg(not(PyPy))]
pub fn filename(&self) -> PyResult<&str> {
unsafe {
self.py()
.from_owned_ptr_or_err::<PyString>(ffi::PyModule_GetFilenameObject(self.as_ptr()))?
.to_str()
}
self.as_borrowed().filename()?.into_gil_ref().to_str()
}
/// Adds an attribute to the module.
@ -239,10 +212,7 @@ impl PyModule {
where
V: IntoPy<PyObject>,
{
self.index()?
.append(name)
.expect("could not append __name__ to __all__");
self.setattr(name, value.into_py(self.py()))
self.as_borrowed().add(name, value)
}
/// Adds a new class to the module.
@ -287,8 +257,7 @@ impl PyModule {
where
T: PyClass,
{
let py = self.py();
self.add(T::NAME, T::lazy_type_object().get_or_try_init(py)?)
self.as_borrowed().add_class::<T>()
}
/// Adds a function or a (sub)module to a module, using the functions name as name.
@ -298,14 +267,7 @@ impl PyModule {
where
T: IntoPyCallbackOutput<PyObject>,
{
self._add_wrapped(wrapper(self.py()).convert(self.py())?)
}
fn _add_wrapped(&self, object: PyObject) -> PyResult<()> {
let py = self.py();
let name = object.getattr(py, __name__(py))?;
let name = name.extract(py)?;
self.add(name, object)
self.as_borrowed().add_wrapped(wrapper)
}
/// Adds a submodule to a module.
@ -349,8 +311,7 @@ impl PyModule {
/// [1]: https://github.com/PyO3/pyo3/issues/759
/// [2]: https://github.com/PyO3/pyo3/issues/1517#issuecomment-808664021
pub fn add_submodule(&self, module: &PyModule) -> PyResult<()> {
let name = module.name()?;
self.add(name, module)
self.as_borrowed().add_submodule(&module.as_borrowed())
}
/// Add a function to a module.
@ -388,8 +349,314 @@ impl PyModule {
/// [1]: crate::prelude::pyfunction
/// [2]: crate::wrap_pyfunction
pub fn add_function<'a>(&'a self, fun: &'a PyCFunction) -> PyResult<()> {
let name = fun.getattr(__name__(self.py()))?.extract()?;
self.add(name, fun)
self.as_borrowed().add_function(&fun.as_borrowed())
}
}
/// Implementation of functionality for [`PyModule`].
///
/// These methods are defined for the `Bound<'py, PyModule>` smart pointer, so to use method call
/// syntax these methods are separated into a trait, because stable Rust does not yet support
/// `arbitrary_self_types`.
#[doc(alias = "PyModule")]
pub trait PyModuleMethods<'py> {
/// Returns the module's `__dict__` attribute, which contains the module's symbol table.
fn dict(&self) -> Bound<'py, PyDict>;
/// Returns the index (the `__all__` attribute) of the module,
/// creating one if needed.
///
/// `__all__` declares the items that will be imported with `from my_module import *`.
fn index(&self) -> PyResult<Bound<'py, PyList>>;
/// Returns the name (the `__name__` attribute) of the module.
///
/// May fail if the module does not have a `__name__` attribute.
fn name(&self) -> PyResult<Bound<'py, PyString>>;
/// Returns the filename (the `__file__` attribute) of the module.
///
/// May fail if the module does not have a `__file__` attribute.
#[cfg(not(PyPy))]
fn filename(&self) -> PyResult<Bound<'py, PyString>>;
/// Adds an attribute to the module.
///
/// For adding classes, functions or modules, prefer to use [`PyModule::add_class`],
/// [`PyModule::add_function`] or [`PyModule::add_submodule`] instead, respectively.
///
/// # Examples
///
/// ```rust
/// use pyo3::prelude::*;
///
/// #[pymodule]
/// fn my_module(_py: Python<'_>, module: &PyModule) -> PyResult<()> {
/// module.add("c", 299_792_458)?;
/// Ok(())
/// }
/// ```
///
/// Python code can then do the following:
///
/// ```python
/// from my_module import c
///
/// print("c is", c)
/// ```
///
/// This will result in the following output:
///
/// ```text
/// c is 299792458
/// ```
fn add<N, V>(&self, name: N, value: V) -> PyResult<()>
where
N: IntoPy<Py<PyString>>,
V: IntoPy<PyObject>;
/// Adds a new class to the module.
///
/// Notice that this method does not take an argument.
/// Instead, this method is *generic*, and requires us to use the
/// "turbofish" syntax to specify the class we want to add.
///
/// # Examples
///
/// ```rust
/// use pyo3::prelude::*;
///
/// #[pyclass]
/// struct Foo { /* fields omitted */ }
///
/// #[pymodule]
/// fn my_module(_py: Python<'_>, module: &PyModule) -> PyResult<()> {
/// module.add_class::<Foo>()?;
/// Ok(())
/// }
/// ```
///
/// Python code can see this class as such:
/// ```python
/// from my_module import Foo
///
/// print("Foo is", Foo)
/// ```
///
/// This will result in the following output:
/// ```text
/// Foo is <class 'builtins.Foo'>
/// ```
///
/// Note that as we haven't defined a [constructor][1], Python code can't actually
/// make an *instance* of `Foo` (or *get* one for that matter, as we haven't exported
/// anything that can return instances of `Foo`).
///
/// [1]: https://pyo3.rs/latest/class.html#constructor
fn add_class<T>(&self) -> PyResult<()>
where
T: PyClass;
/// Adds a function or a (sub)module to a module, using the functions name as name.
///
/// Prefer to use [`PyModule::add_function`] and/or [`PyModule::add_submodule`] instead.
fn add_wrapped<T>(&self, wrapper: &impl Fn(Python<'py>) -> T) -> PyResult<()>
where
T: IntoPyCallbackOutput<PyObject>;
/// Adds a submodule to a module.
///
/// This is especially useful for creating module hierarchies.
///
/// Note that this doesn't define a *package*, so this won't allow Python code
/// to directly import submodules by using
/// <span style="white-space: pre">`from my_module import submodule`</span>.
/// For more information, see [#759][1] and [#1517][2].
///
/// # Examples
///
/// ```rust
/// use pyo3::prelude::*;
///
/// #[pymodule]
/// fn my_module(py: Python<'_>, module: &PyModule) -> PyResult<()> {
/// let submodule = PyModule::new(py, "submodule")?;
/// submodule.add("super_useful_constant", "important")?;
///
/// module.add_submodule(submodule)?;
/// Ok(())
/// }
/// ```
///
/// Python code can then do the following:
///
/// ```python
/// import my_module
///
/// print("super_useful_constant is", my_module.submodule.super_useful_constant)
/// ```
///
/// This will result in the following output:
///
/// ```text
/// super_useful_constant is important
/// ```
///
/// [1]: https://github.com/PyO3/pyo3/issues/759
/// [2]: https://github.com/PyO3/pyo3/issues/1517#issuecomment-808664021
fn add_submodule(&self, module: &Bound<'_, PyModule>) -> PyResult<()>;
/// Add a function to a module.
///
/// Note that this also requires the [`wrap_pyfunction!`][2] macro
/// to wrap a function annotated with [`#[pyfunction]`][1].
///
/// ```rust
/// use pyo3::prelude::*;
///
/// #[pyfunction]
/// fn say_hello() {
/// println!("Hello world!")
/// }
/// #[pymodule]
/// fn my_module(_py: Python<'_>, module: &PyModule) -> PyResult<()> {
/// module.add_function(wrap_pyfunction!(say_hello, module)?)
/// }
/// ```
///
/// Python code can then do the following:
///
/// ```python
/// from my_module import say_hello
///
/// say_hello()
/// ```
///
/// This will result in the following output:
///
/// ```text
/// Hello world!
/// ```
///
/// [1]: crate::prelude::pyfunction
/// [2]: crate::wrap_pyfunction
fn add_function(&self, fun: &Bound<'_, PyCFunction>) -> PyResult<()>;
}
impl<'py> PyModuleMethods<'py> for Bound<'py, PyModule> {
fn dict(&self) -> Bound<'py, PyDict> {
unsafe {
// PyModule_GetDict returns borrowed ptr; must make owned for safety (see #890).
ffi::PyModule_GetDict(self.as_ptr())
.assume_borrowed(self.py())
.to_owned()
.downcast_into_unchecked()
}
}
fn index(&self) -> PyResult<Bound<'py, PyList>> {
let __all__ = __all__(self.py());
match self.getattr(__all__) {
Ok(idx) => idx.downcast_into().map_err(PyErr::from),
Err(err) => {
if err.is_instance_of::<exceptions::PyAttributeError>(self.py()) {
let l = PyList::empty(self.py()).as_borrowed().to_owned();
self.setattr(__all__, &l).map_err(PyErr::from)?;
Ok(l)
} else {
Err(err)
}
}
}
}
fn name(&self) -> PyResult<Bound<'py, PyString>> {
#[cfg(not(PyPy))]
{
use crate::py_result_ext::PyResultExt;
unsafe {
ffi::PyModule_GetNameObject(self.as_ptr())
.assume_owned_or_err(self.py())
.downcast_into_unchecked()
}
}
#[cfg(PyPy)]
{
self.dict()
.get_item("__name__")
.map_err(|_| exceptions::PyAttributeError::new_err("__name__"))?
.downcast_into()
.map_err(PyErr::from)
}
}
#[cfg(not(PyPy))]
fn filename(&self) -> PyResult<Bound<'py, PyString>> {
use crate::py_result_ext::PyResultExt;
unsafe {
ffi::PyModule_GetFilenameObject(self.as_ptr())
.assume_owned_or_err(self.py())
.downcast_into_unchecked()
}
}
fn add<N, V>(&self, name: N, value: V) -> PyResult<()>
where
N: IntoPy<Py<PyString>>,
V: IntoPy<PyObject>,
{
fn inner(
module: &Bound<'_, PyModule>,
name: Bound<'_, PyString>,
value: Bound<'_, PyAny>,
) -> PyResult<()> {
module
.index()?
.append(&name)
.expect("could not append __name__ to __all__");
module.setattr(name, value.into_py(module.py()))
}
let py = self.py();
inner(
self,
name.into_py(py).into_bound(py),
value.into_py(py).into_bound(py),
)
}
fn add_class<T>(&self) -> PyResult<()>
where
T: PyClass,
{
let py = self.py();
self.add(T::NAME, T::lazy_type_object().get_or_try_init(py)?)
}
fn add_wrapped<T>(&self, wrapper: &impl Fn(Python<'py>) -> T) -> PyResult<()>
where
T: IntoPyCallbackOutput<PyObject>,
{
fn inner(module: &Bound<'_, PyModule>, object: Bound<'_, PyAny>) -> PyResult<()> {
let name = object.getattr(__name__(module.py()))?;
module.add(name.downcast_into::<PyString>()?, object)
}
let py = self.py();
inner(self, wrapper(py).convert(py)?.into_bound(py))
}
fn add_submodule(&self, module: &Bound<'_, PyModule>) -> PyResult<()> {
let name = module.name()?;
self.add(name, module)
}
fn add_function(&self, fun: &Bound<'_, PyCFunction>) -> PyResult<()> {
let name = fun.getattr(__name__(self.py()))?;
self.add(name.downcast_into::<PyString>()?, fun)
}
}
@ -412,4 +679,13 @@ mod tests {
assert_eq!(builtins.name().unwrap(), "builtins");
})
}
#[test]
#[cfg(not(PyPy))]
fn module_filename() {
Python::with_gil(|py| {
let site = PyModule::import(py, "site").unwrap();
assert!(site.filename().unwrap().ends_with("site.py"));
})
}
}

View File

@ -33,7 +33,7 @@ fn test_compile_errors() {
t.compile_fail("tests/ui/invalid_pymethod_receiver.rs");
t.compile_fail("tests/ui/missing_intopy.rs");
// adding extra error conversion impls changes the output
#[cfg(not(any(windows, feature = "eyre", feature = "anyhow")))]
#[cfg(not(any(windows, feature = "eyre", feature = "anyhow", Py_LIMITED_API)))]
t.compile_fail("tests/ui/invalid_result_conversion.rs");
t.compile_fail("tests/ui/not_send.rs");
t.compile_fail("tests/ui/not_send2.rs");