-
Notifications
You must be signed in to change notification settings - Fork 783
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
Pymodule bound #3897
Pymodule bound #3897
Changes from all commits
9db0ea9
a05342e
e310b4a
b67f93f
90a1efe
4af75fe
df1da21
f7a4e82
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -268,12 +268,12 @@ pub fn impl_wrap_pyfunction( | |
#[doc(hidden)] | ||
#vis mod #name { | ||
pub(crate) struct MakeDef; | ||
pub const DEF: #krate::impl_::pyfunction::PyMethodDef = MakeDef::DEF; | ||
pub const DEF: #krate::impl_::pymethods::PyMethodDef = MakeDef::DEF; | ||
|
||
pub fn add_to_module(module: &#krate::Bound<'_, #krate::types::PyModule>) -> #krate::PyResult<()> { | ||
use #krate::prelude::PyModuleMethods; | ||
use ::std::convert::Into; | ||
module.add_function(&#krate::types::PyCFunction::internal_new(&DEF, module.as_gil_ref().into())?) | ||
module.add_function(#krate::types::PyCFunction::internal_new(&DEF, module.as_gil_ref().into())?) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a bit sad to still need this |
||
} | ||
} | ||
|
||
|
@@ -284,7 +284,7 @@ pub fn impl_wrap_pyfunction( | |
const _: () = { | ||
use #krate as _pyo3; | ||
impl #name::MakeDef { | ||
const DEF: #krate::impl_::pyfunction::PyMethodDef = #methoddef; | ||
const DEF: #krate::impl_::pymethods::PyMethodDef = #methoddef; | ||
} | ||
|
||
#[allow(non_snake_case)] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,68 @@ | ||
use crate::{derive_utils::PyFunctionArguments, types::PyCFunction, PyResult}; | ||
use crate::{ | ||
types::{PyCFunction, PyModule}, | ||
Borrowed, Bound, PyResult, Python, | ||
}; | ||
|
||
pub use crate::impl_::pymethods::PyMethodDef; | ||
|
||
pub fn _wrap_pyfunction<'a>( | ||
method_def: &PyMethodDef, | ||
py_or_module: impl Into<PyFunctionArguments<'a>>, | ||
) -> PyResult<&'a PyCFunction> { | ||
PyCFunction::internal_new(method_def, py_or_module.into()).map(|x| x.into_gil_ref()) | ||
/// 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> { | ||
fn wrap_pyfunction(self, method_def: &PyMethodDef) -> PyResult<T>; | ||
} | ||
|
||
Comment on lines
+10
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Very interesting solution! I guess There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes actually I was thinking that later on today, I had generic originally because I wanted to play with type inference but given that each type now implements only once we could go for the associated type. |
||
impl<'py> WrapPyFunctionArg<'py, Bound<'py, PyCFunction>> for Bound<'py, PyModule> { | ||
fn wrap_pyfunction(self, method_def: &PyMethodDef) -> PyResult<Bound<'py, PyCFunction>> { | ||
PyCFunction::internal_new_bound(self.py(), method_def, Some(&self)) | ||
} | ||
} | ||
|
||
impl<'py> WrapPyFunctionArg<'py, Bound<'py, PyCFunction>> for &'_ Bound<'py, PyModule> { | ||
fn wrap_pyfunction(self, method_def: &PyMethodDef) -> PyResult<Bound<'py, PyCFunction>> { | ||
PyCFunction::internal_new_bound(self.py(), method_def, Some(self)) | ||
} | ||
} | ||
|
||
impl<'py> WrapPyFunctionArg<'py, Bound<'py, PyCFunction>> for Borrowed<'_, 'py, PyModule> { | ||
fn wrap_pyfunction(self, method_def: &PyMethodDef) -> PyResult<Bound<'py, PyCFunction>> { | ||
PyCFunction::internal_new_bound(self.py(), method_def, Some(&self)) | ||
} | ||
} | ||
|
||
impl<'py> WrapPyFunctionArg<'py, Bound<'py, PyCFunction>> for &'_ Borrowed<'_, 'py, PyModule> { | ||
fn wrap_pyfunction(self, method_def: &PyMethodDef) -> PyResult<Bound<'py, PyCFunction>> { | ||
PyCFunction::internal_new_bound(self.py(), method_def, Some(self)) | ||
} | ||
} | ||
|
||
// For Python<'py>, only the GIL Ref form exists to avoid causing type inference to kick in. | ||
// The `wrap_pyfunction_bound!` macro is needed for the Bound form. | ||
impl<'py> WrapPyFunctionArg<'py, &'py PyCFunction> for Python<'py> { | ||
fn wrap_pyfunction(self, method_def: &PyMethodDef) -> PyResult<&'py PyCFunction> { | ||
PyCFunction::internal_new(method_def, self.into()).map(Bound::into_gil_ref) | ||
} | ||
} | ||
|
||
impl<'py> WrapPyFunctionArg<'py, &'py PyCFunction> for &'py PyModule { | ||
fn wrap_pyfunction(self, method_def: &PyMethodDef) -> PyResult<&'py PyCFunction> { | ||
PyCFunction::internal_new(method_def, self.into()).map(Bound::into_gil_ref) | ||
} | ||
} | ||
|
||
/// Helper for `wrap_pyfunction_bound!` to guarantee return type of `Bound<'py, PyCFunction>`. | ||
pub struct OnlyBound<T>(pub T); | ||
|
||
impl<'py, T> WrapPyFunctionArg<'py, Bound<'py, PyCFunction>> for OnlyBound<T> | ||
where | ||
T: WrapPyFunctionArg<'py, Bound<'py, PyCFunction>>, | ||
{ | ||
fn wrap_pyfunction(self, method_def: &PyMethodDef) -> PyResult<Bound<'py, PyCFunction>> { | ||
WrapPyFunctionArg::wrap_pyfunction(self.0, method_def) | ||
} | ||
} | ||
|
||
impl<'py> WrapPyFunctionArg<'py, Bound<'py, PyCFunction>> for OnlyBound<Python<'py>> { | ||
fn wrap_pyfunction(self, method_def: &PyMethodDef) -> PyResult<Bound<'py, PyCFunction>> { | ||
PyCFunction::internal_new_bound(self.0, method_def, None) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -121,18 +121,57 @@ macro_rules! py_run_impl { | |
/// Wraps a Rust function annotated with [`#[pyfunction]`](macro@crate::pyfunction). | ||
/// | ||
/// This can be used with [`PyModule::add_function`](crate::types::PyModule::add_function) to add free | ||
/// functions to a [`PyModule`](crate::types::PyModule) - see its documentation for more information. | ||
/// functions to a [`PyModule`](crate::types::PyModule) - see its documentation for more | ||
/// information. | ||
/// | ||
/// During the migration from the GIL Ref API to the Bound API, the return type of this macro will | ||
/// be either the `&'py PyModule` GIL Ref or `Bound<'py, PyModule>` according to the second | ||
/// argument. | ||
/// | ||
/// For backwards compatibility, if the second argument is `Python<'py>` then the return type will | ||
/// be `&'py PyModule` GIL Ref. To get `Bound<'py, PyModule>`, use the [`crate::wrap_pyfunction_bound!`] | ||
/// macro instead. | ||
#[macro_export] | ||
macro_rules! wrap_pyfunction { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one should now get a deprecation warning I thing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good spot! I think maybe this makes sense to do in a separate PR since it'll cause a lot of churn in tests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like that is #3899 |
||
($function:path) => { | ||
&|py_or_module| { | ||
use $function as wrapped_pyfunction; | ||
$crate::impl_::pyfunction::_wrap_pyfunction(&wrapped_pyfunction::DEF, py_or_module) | ||
$crate::impl_::pyfunction::WrapPyFunctionArg::wrap_pyfunction( | ||
py_or_module, | ||
&wrapped_pyfunction::DEF, | ||
) | ||
} | ||
}; | ||
($function:path, $py_or_module:expr) => {{ | ||
use $function as wrapped_pyfunction; | ||
$crate::impl_::pyfunction::WrapPyFunctionArg::wrap_pyfunction( | ||
$py_or_module, | ||
&wrapped_pyfunction::DEF, | ||
) | ||
}}; | ||
} | ||
|
||
/// Wraps a Rust function annotated with [`#[pyfunction]`](macro@crate::pyfunction). | ||
/// | ||
/// This can be used with [`PyModule::add_function`](crate::types::PyModule::add_function) to add free | ||
/// functions to a [`PyModule`](crate::types::PyModule) - see its documentation for more information. | ||
#[macro_export] | ||
macro_rules! wrap_pyfunction_bound { | ||
($function:path) => { | ||
&|py_or_module| { | ||
use $function as wrapped_pyfunction; | ||
$crate::impl_::pyfunction::WrapPyFunctionArg::wrap_pyfunction( | ||
$crate::impl_::pyfunction::OnlyBound(py_or_module), | ||
&wrapped_pyfunction::DEF, | ||
) | ||
} | ||
}; | ||
($function:path, $py_or_module:expr) => {{ | ||
use $function as wrapped_pyfunction; | ||
$crate::impl_::pyfunction::_wrap_pyfunction(&wrapped_pyfunction::DEF, $py_or_module) | ||
$crate::impl_::pyfunction::WrapPyFunctionArg::wrap_pyfunction( | ||
$crate::impl_::pyfunction::OnlyBound($py_or_module), | ||
&wrapped_pyfunction::DEF, | ||
) | ||
}}; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,10 +3,11 @@ use crate::ffi_ptr_ext::FfiPtrExt; | |
use crate::methods::PyMethodDefDestructor; | ||
use crate::py_result_ext::PyResultExt; | ||
use crate::types::capsule::PyCapsuleMethods; | ||
use crate::types::module::PyModuleMethods; | ||
use crate::{ | ||
ffi, | ||
impl_::pymethods::{self, PyMethodDef}, | ||
types::{PyCapsule, PyDict, PyString, PyTuple}, | ||
types::{PyCapsule, PyDict, PyModule, PyString, PyTuple}, | ||
}; | ||
use crate::{Bound, IntoPy, Py, PyAny, PyResult, Python}; | ||
use std::cell::UnsafeCell; | ||
|
@@ -33,23 +34,33 @@ impl PyCFunction { | |
doc: &'static str, | ||
py_or_module: PyFunctionArguments<'a>, | ||
) -> PyResult<&'a Self> { | ||
Self::new_with_keywords_bound(fun, name, doc, py_or_module).map(Bound::into_gil_ref) | ||
Self::internal_new( | ||
&PyMethodDef::cfunction_with_keywords( | ||
name, | ||
pymethods::PyCFunctionWithKeywords(fun), | ||
doc, | ||
), | ||
py_or_module, | ||
) | ||
.map(Bound::into_gil_ref) | ||
} | ||
|
||
/// Create a new built-in function with keywords (*args and/or **kwargs). | ||
pub fn new_with_keywords_bound<'a>( | ||
pub fn new_with_keywords_bound<'py>( | ||
py: Python<'py>, | ||
fun: ffi::PyCFunctionWithKeywords, | ||
name: &'static str, | ||
doc: &'static str, | ||
py_or_module: PyFunctionArguments<'a>, | ||
) -> PyResult<Bound<'a, Self>> { | ||
Self::internal_new( | ||
module: Option<&Bound<'py, PyModule>>, | ||
) -> PyResult<Bound<'py, Self>> { | ||
Self::internal_new_bound( | ||
py, | ||
&PyMethodDef::cfunction_with_keywords( | ||
name, | ||
pymethods::PyCFunctionWithKeywords(fun), | ||
doc, | ||
), | ||
py_or_module, | ||
module, | ||
) | ||
} | ||
|
||
|
@@ -67,19 +78,25 @@ impl PyCFunction { | |
doc: &'static str, | ||
py_or_module: PyFunctionArguments<'a>, | ||
) -> PyResult<&'a Self> { | ||
Self::new_bound(fun, name, doc, py_or_module).map(Bound::into_gil_ref) | ||
Self::internal_new( | ||
&PyMethodDef::noargs(name, pymethods::PyCFunction(fun), doc), | ||
py_or_module, | ||
) | ||
.map(Bound::into_gil_ref) | ||
} | ||
|
||
/// Create a new built-in function which takes no arguments. | ||
pub fn new_bound<'a>( | ||
pub fn new_bound<'py>( | ||
py: Python<'py>, | ||
fun: ffi::PyCFunction, | ||
name: &'static str, | ||
doc: &'static str, | ||
py_or_module: PyFunctionArguments<'a>, | ||
) -> PyResult<Bound<'a, Self>> { | ||
Self::internal_new( | ||
module: Option<&Bound<'py, PyModule>>, | ||
) -> PyResult<Bound<'py, Self>> { | ||
Self::internal_new_bound( | ||
py, | ||
&PyMethodDef::noargs(name, pymethods::PyCFunction(fun), doc), | ||
py_or_module, | ||
module, | ||
) | ||
} | ||
|
||
|
@@ -189,6 +206,35 @@ impl PyCFunction { | |
.downcast_into_unchecked() | ||
} | ||
} | ||
|
||
#[doc(hidden)] | ||
pub(crate) fn internal_new_bound<'py>( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we don't need this. We can change the signature of |
||
py: Python<'py>, | ||
method_def: &PyMethodDef, | ||
module: Option<&Bound<'py, PyModule>>, | ||
) -> PyResult<Bound<'py, Self>> { | ||
let (mod_ptr, module_name): (_, Option<Py<PyString>>) = if let Some(m) = module { | ||
let mod_ptr = m.as_ptr(); | ||
(mod_ptr, Some(m.name()?.into_py(py))) | ||
} else { | ||
(std::ptr::null_mut(), None) | ||
}; | ||
let (def, destructor) = method_def.as_method_def()?; | ||
|
||
// FIXME: stop leaking the def and destructor | ||
let def = Box::into_raw(Box::new(def)); | ||
std::mem::forget(destructor); | ||
|
||
let module_name_ptr = module_name | ||
.as_ref() | ||
.map_or(std::ptr::null_mut(), Py::as_ptr); | ||
|
||
unsafe { | ||
ffi::PyCFunction_NewEx(def, mod_ptr, module_name_ptr) | ||
.assume_owned_or_err(py) | ||
.downcast_into_unchecked() | ||
} | ||
} | ||
} | ||
|
||
fn closure_capsule_name() -> &'static CStr { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -399,7 +399,8 @@ impl PyModule { | |
/// [1]: crate::prelude::pyfunction | ||
/// [2]: crate::wrap_pyfunction | ||
pub fn add_function<'a>(&'a self, fun: &'a PyCFunction) -> PyResult<()> { | ||
self.as_borrowed().add_function(&fun.as_borrowed()) | ||
let name = fun.getattr(__name__(self.py()))?.extract()?; | ||
self.add(name, fun) | ||
} | ||
} | ||
|
||
|
@@ -590,7 +591,7 @@ pub trait PyModuleMethods<'py> { | |
/// | ||
/// [1]: crate::prelude::pyfunction | ||
/// [2]: crate::wrap_pyfunction | ||
fn add_function(&self, fun: &Bound<'_, PyCFunction>) -> PyResult<()>; | ||
fn add_function(&self, fun: Bound<'_, PyCFunction>) -> PyResult<()>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess this is just to make it nicer, so that we don't have to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I think it's probably worth making this change? It's not like we expect this to be called in a hot loop. I guess we could also have |
||
} | ||
|
||
impl<'py> PyModuleMethods<'py> for Bound<'py, PyModule> { | ||
|
@@ -700,7 +701,7 @@ impl<'py> PyModuleMethods<'py> for Bound<'py, PyModule> { | |
self.add(name, module) | ||
} | ||
|
||
fn add_function(&self, fun: &Bound<'_, PyCFunction>) -> PyResult<()> { | ||
fn add_function(&self, fun: Bound<'_, PyCFunction>) -> PyResult<()> { | ||
let name = fun.getattr(__name__(self.py()))?; | ||
self.add(name.downcast_into::<PyString>()?, fun) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm hesitant to add this kind of
use
statement, which transparently injects things into the namespace of user code and so would seem to violate hygiene (see discussion in the follow-up PR here: #3899 (comment)). An alternative would be to use fully-qualified references to trait methods in the macro-generated code. But as @Icxolu noted in that discussion, the patterns around these new*Methods
traits are not very well established, so I could be persuaded :-)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, after that discussion I tend to agree, that we should not inject these traits silently. If we still want to do it we should probably at least import them anonymously e.g. as
use "Trait" as _
. I would not consider it a blocker here, we can change in a followup if needed. Another thing would be if we should seal these traits, but that's another discussion entirely.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Maybe a good solution here it to write a private trait in
impl_/pymodule.rs
which has just the methods we need implemented for bothBound<'_, PyModule>
and&'py PyModule
, which we can then call directly without imports.(This is actually probably very similar to what @LilyFoote had already done with the
#[doc(hidden)] fn wrap_pyfunction
, just located as a private trait inside the impl codebase.)