pyo3_path, part 2: add pyo3_path options and use them.
This commit is contained in:
parent
e2c0e34837
commit
e4f608f605
|
@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- Add `Py::setattr` method. [#2009](https://github.com/PyO3/pyo3/pull/2009)
|
||||
- Add `PyCapsule`, exposing the [Capsule API](https://docs.python.org/3/c-api/capsule.html#capsules). [#1980](https://github.com/PyO3/pyo3/pull/1980)
|
||||
- All PyO3 proc-macros except the deprecated `#[pyproto]` now accept a supplemental attribute `#[pyo3(pyo3_path = "some::path")]` that specifies
|
||||
where to find the `pyo3` crate, in case it has been renamed or is re-exported and not found at the crate root. [#2022](https://github.com/PyO3/pyo3/pull/2022)
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
@ -139,3 +139,23 @@ a: <builtins.Inner object at 0x0000020044FCC670>
|
|||
b: <builtins.Inner object at 0x0000020044FCC670>
|
||||
```
|
||||
The downside to this approach is that any Rust code working on the `Outer` struct now has to acquire the GIL to do anything with its field.
|
||||
|
||||
## I want to use the `pyo3` crate re-exported from from dependency but the proc-macros fail!
|
||||
|
||||
All PyO3 proc-macros (`#[pyclass]`, `#[pyfunction]`, `#[derive(FromPyObject)]`
|
||||
and so on) expect the `pyo3` crate to be available under that name in your crate
|
||||
root, which is the normal situation when `pyo3` is a direct dependency of your
|
||||
crate.
|
||||
|
||||
However, when the dependency is renamed, or your crate only indirectly depends
|
||||
on `pyo3`, you need to let the macro code know where to find the crate. This is
|
||||
done with the `pyo3_path` attribute:
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
# pub extern crate pyo3;
|
||||
# mod reexported { pub use ::pyo3; }
|
||||
#[pyclass]
|
||||
#[pyo3(pyo3_path = "reexported::pyo3")]
|
||||
struct MyClass;
|
||||
```
|
||||
|
|
|
@ -2,7 +2,7 @@ use syn::{
|
|||
parse::{Parse, ParseStream},
|
||||
punctuated::Punctuated,
|
||||
token::Comma,
|
||||
Attribute, ExprPath, Ident, LitStr, Result, Token,
|
||||
Attribute, ExprPath, Ident, LitStr, Path, Result, Token,
|
||||
};
|
||||
|
||||
pub mod kw {
|
||||
|
@ -17,6 +17,7 @@ pub mod kw {
|
|||
syn::custom_keyword!(signature);
|
||||
syn::custom_keyword!(text_signature);
|
||||
syn::custom_keyword!(transparent);
|
||||
syn::custom_keyword!(pyo3_path);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -43,6 +44,19 @@ impl Parse for NameAttribute {
|
|||
}
|
||||
}
|
||||
|
||||
/// For specifying the path to the pyo3 crate.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PyO3PathAttribute(pub Path);
|
||||
|
||||
impl Parse for PyO3PathAttribute {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let _: kw::pyo3_path = input.parse()?;
|
||||
let _: Token![=] = input.parse()?;
|
||||
let string_literal: LitStr = input.parse()?;
|
||||
string_literal.parse().map(PyO3PathAttribute)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TextSignatureAttribute {
|
||||
pub kw: kw::text_signature,
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use crate::attributes::{self, get_pyo3_options, FromPyWithAttribute};
|
||||
use crate::{
|
||||
attributes::{self, get_pyo3_options, FromPyWithAttribute, PyO3PathAttribute},
|
||||
utils::get_pyo3_path,
|
||||
};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
|
@ -300,20 +303,25 @@ impl<'a> Container<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ContainerOptions {
|
||||
/// Treat the Container as a Wrapper, directly extract its fields from the input object.
|
||||
transparent: bool,
|
||||
/// Change the name of an enum variant in the generated error message.
|
||||
annotation: Option<syn::LitStr>,
|
||||
/// Change the path for the pyo3 crate
|
||||
pyo3_path: Option<PyO3PathAttribute>,
|
||||
}
|
||||
|
||||
/// Attributes for deriving FromPyObject scoped on containers.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
enum ContainerPyO3Attribute {
|
||||
/// Treat the Container as a Wrapper, directly extract its fields from the input object.
|
||||
Transparent(attributes::kw::transparent),
|
||||
/// Change the name of an enum variant in the generated error message.
|
||||
ErrorAnnotation(LitStr),
|
||||
/// Change the path for the pyo3 crate
|
||||
PyO3Path(PyO3PathAttribute),
|
||||
}
|
||||
|
||||
impl Parse for ContainerPyO3Attribute {
|
||||
|
@ -326,6 +334,8 @@ impl Parse for ContainerPyO3Attribute {
|
|||
let _: attributes::kw::annotation = input.parse()?;
|
||||
let _: Token![=] = input.parse()?;
|
||||
input.parse().map(ContainerPyO3Attribute::ErrorAnnotation)
|
||||
} else if lookahead.peek(attributes::kw::pyo3_path) {
|
||||
input.parse().map(ContainerPyO3Attribute::PyO3Path)
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
|
@ -334,10 +344,8 @@ impl Parse for ContainerPyO3Attribute {
|
|||
|
||||
impl ContainerOptions {
|
||||
fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
|
||||
let mut options = ContainerOptions {
|
||||
transparent: false,
|
||||
annotation: None,
|
||||
};
|
||||
let mut options = ContainerOptions::default();
|
||||
|
||||
for attr in attrs {
|
||||
if let Some(pyo3_attrs) = get_pyo3_options(attr)? {
|
||||
for pyo3_attr in pyo3_attrs {
|
||||
|
@ -356,6 +364,13 @@ impl ContainerOptions {
|
|||
);
|
||||
options.annotation = Some(lit_str);
|
||||
}
|
||||
ContainerPyO3Attribute::PyO3Path(path) => {
|
||||
ensure_spanned!(
|
||||
options.pyo3_path.is_none(),
|
||||
path.0.span() => "`pyo3_path` may only be provided once"
|
||||
);
|
||||
options.pyo3_path = Some(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -499,13 +514,18 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
|
|||
.predicates
|
||||
.push(parse_quote!(#gen_ident: FromPyObject<#lt_param>))
|
||||
}
|
||||
let options = ContainerOptions::from_attrs(&tokens.attrs)?;
|
||||
let pyo3_path = get_pyo3_path(&options.pyo3_path);
|
||||
let derives = match &tokens.data {
|
||||
syn::Data::Enum(en) => {
|
||||
if options.transparent || options.annotation.is_some() {
|
||||
bail_spanned!(tokens.span() => "`transparent` or `annotation` is not supported \
|
||||
at top level for enums");
|
||||
}
|
||||
let en = Enum::new(en, &tokens.ident)?;
|
||||
en.build()
|
||||
}
|
||||
syn::Data::Struct(st) => {
|
||||
let options = ContainerOptions::from_attrs(&tokens.attrs)?;
|
||||
if let Some(lit_str) = &options.annotation {
|
||||
bail_spanned!(lit_str.span() => "`annotation` is unsupported for structs");
|
||||
}
|
||||
|
@ -520,11 +540,15 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
|
|||
|
||||
let ident = &tokens.ident;
|
||||
Ok(quote!(
|
||||
#[automatically_derived]
|
||||
impl#trait_generics _pyo3::FromPyObject<#lt_param> for #ident#generics #where_clause {
|
||||
fn extract(obj: &#lt_param _pyo3::PyAny) -> _pyo3::PyResult<Self> {
|
||||
#derives
|
||||
const _: () = {
|
||||
use #pyo3_path as _pyo3;
|
||||
|
||||
#[automatically_derived]
|
||||
impl#trait_generics _pyo3::FromPyObject<#lt_param> for #ident#generics #where_clause {
|
||||
fn extract(obj: &#lt_param _pyo3::PyAny) -> _pyo3::PyResult<Self> {
|
||||
#derives
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
))
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::deprecations::Deprecation;
|
|||
use crate::params::{accept_args_kwargs, impl_arg_params};
|
||||
use crate::pyfunction::PyFunctionOptions;
|
||||
use crate::pyfunction::{PyFunctionArgPyO3Attributes, PyFunctionSignature};
|
||||
use crate::utils::{self, PythonDoc};
|
||||
use crate::utils::{self, get_pyo3_path, PythonDoc};
|
||||
use crate::{deprecations::Deprecations, pyfunction::Argument};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::ToTokens;
|
||||
|
@ -228,6 +228,7 @@ pub struct FnSpec<'a> {
|
|||
pub deprecations: Deprecations,
|
||||
pub convention: CallingConvention,
|
||||
pub text_signature: Option<TextSignatureAttribute>,
|
||||
pub pyo3_path: syn::Path,
|
||||
}
|
||||
|
||||
pub fn get_return_info(output: &syn::ReturnType) -> syn::Type {
|
||||
|
@ -254,12 +255,14 @@ pub fn parse_method_receiver(arg: &syn::FnArg) -> Result<SelfType> {
|
|||
impl<'a> FnSpec<'a> {
|
||||
/// Parser function signature and function attributes
|
||||
pub fn parse(
|
||||
// Signature is mutable to remove the `Python` argument.
|
||||
sig: &'a mut syn::Signature,
|
||||
meth_attrs: &mut Vec<syn::Attribute>,
|
||||
options: PyFunctionOptions,
|
||||
) -> Result<FnSpec<'a>> {
|
||||
let PyFunctionOptions {
|
||||
text_signature,
|
||||
pyo3_path,
|
||||
name,
|
||||
mut deprecations,
|
||||
..
|
||||
|
@ -278,6 +281,7 @@ impl<'a> FnSpec<'a> {
|
|||
let name = &sig.ident;
|
||||
let ty = get_return_info(&sig.output);
|
||||
let python_name = python_name.as_ref().unwrap_or(name).unraw();
|
||||
let pyo3_path = get_pyo3_path(&pyo3_path);
|
||||
|
||||
let doc = utils::get_doc(
|
||||
meth_attrs,
|
||||
|
@ -311,6 +315,7 @@ impl<'a> FnSpec<'a> {
|
|||
doc,
|
||||
deprecations,
|
||||
text_signature,
|
||||
pyo3_path,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -472,14 +477,16 @@ impl<'a> FnSpec<'a> {
|
|||
};
|
||||
let rust_call =
|
||||
quote! { _pyo3::callback::convert(#py, #rust_name(#self_arg #(#arg_names),*)) };
|
||||
let pyo3_path = &self.pyo3_path;
|
||||
Ok(match self.convention {
|
||||
CallingConvention::Noargs => {
|
||||
quote! {
|
||||
unsafe extern "C" fn #ident (
|
||||
_slf: *mut _pyo3::ffi::PyObject,
|
||||
_args: *mut _pyo3::ffi::PyObject,
|
||||
) -> *mut _pyo3::ffi::PyObject
|
||||
_slf: *mut #pyo3_path::ffi::PyObject,
|
||||
_args: *mut #pyo3_path::ffi::PyObject,
|
||||
) -> *mut #pyo3_path::ffi::PyObject
|
||||
{
|
||||
use #pyo3_path as _pyo3;
|
||||
#deprecations
|
||||
_pyo3::callback::handle_panic(|#py| {
|
||||
#self_conversion
|
||||
|
@ -492,11 +499,12 @@ impl<'a> FnSpec<'a> {
|
|||
let arg_convert_and_rust_call = impl_arg_params(self, cls, rust_call, &py, true)?;
|
||||
quote! {
|
||||
unsafe extern "C" fn #ident (
|
||||
_slf: *mut _pyo3::ffi::PyObject,
|
||||
_args: *const *mut _pyo3::ffi::PyObject,
|
||||
_nargs: _pyo3::ffi::Py_ssize_t,
|
||||
_kwnames: *mut _pyo3::ffi::PyObject) -> *mut _pyo3::ffi::PyObject
|
||||
_slf: *mut #pyo3_path::ffi::PyObject,
|
||||
_args: *const *mut #pyo3_path::ffi::PyObject,
|
||||
_nargs: #pyo3_path::ffi::Py_ssize_t,
|
||||
_kwnames: *mut #pyo3_path::ffi::PyObject) -> *mut #pyo3_path::ffi::PyObject
|
||||
{
|
||||
use #pyo3_path as _pyo3;
|
||||
#deprecations
|
||||
_pyo3::callback::handle_panic(|#py| {
|
||||
#self_conversion
|
||||
|
@ -519,10 +527,11 @@ impl<'a> FnSpec<'a> {
|
|||
let arg_convert_and_rust_call = impl_arg_params(self, cls, rust_call, &py, false)?;
|
||||
quote! {
|
||||
unsafe extern "C" fn #ident (
|
||||
_slf: *mut _pyo3::ffi::PyObject,
|
||||
_args: *mut _pyo3::ffi::PyObject,
|
||||
_kwargs: *mut _pyo3::ffi::PyObject) -> *mut _pyo3::ffi::PyObject
|
||||
_slf: *mut #pyo3_path::ffi::PyObject,
|
||||
_args: *mut #pyo3_path::ffi::PyObject,
|
||||
_kwargs: *mut #pyo3_path::ffi::PyObject) -> *mut #pyo3_path::ffi::PyObject
|
||||
{
|
||||
use #pyo3_path as _pyo3;
|
||||
#deprecations
|
||||
_pyo3::callback::handle_panic(|#py| {
|
||||
#self_conversion
|
||||
|
@ -539,10 +548,11 @@ impl<'a> FnSpec<'a> {
|
|||
let arg_convert_and_rust_call = impl_arg_params(self, cls, rust_call, &py, false)?;
|
||||
quote! {
|
||||
unsafe extern "C" fn #ident (
|
||||
subtype: *mut _pyo3::ffi::PyTypeObject,
|
||||
_args: *mut _pyo3::ffi::PyObject,
|
||||
_kwargs: *mut _pyo3::ffi::PyObject) -> *mut _pyo3::ffi::PyObject
|
||||
subtype: *mut #pyo3_path::ffi::PyTypeObject,
|
||||
_args: *mut #pyo3_path::ffi::PyObject,
|
||||
_kwargs: *mut #pyo3_path::ffi::PyObject) -> *mut #pyo3_path::ffi::PyObject
|
||||
{
|
||||
use #pyo3_path as _pyo3;
|
||||
#deprecations
|
||||
use _pyo3::callback::IntoPyCallbackOutput;
|
||||
_pyo3::callback::handle_panic(|#py| {
|
||||
|
|
|
@ -2,9 +2,12 @@
|
|||
//! Code generation for the function that initializes a python module and adds classes and function.
|
||||
|
||||
use crate::{
|
||||
attributes::{self, is_attribute_ident, take_attributes, take_pyo3_options, NameAttribute},
|
||||
attributes::{
|
||||
self, is_attribute_ident, take_attributes, take_pyo3_options, NameAttribute,
|
||||
PyO3PathAttribute,
|
||||
},
|
||||
pyfunction::{impl_wrap_pyfunction, PyFunctionOptions},
|
||||
utils::PythonDoc,
|
||||
utils::{get_pyo3_path, PythonDoc},
|
||||
};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::quote;
|
||||
|
@ -16,17 +19,20 @@ use syn::{
|
|||
Ident, Path, Result,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PyModuleOptions {
|
||||
pyo3_path: Option<PyO3PathAttribute>,
|
||||
name: Option<syn::Ident>,
|
||||
}
|
||||
|
||||
impl PyModuleOptions {
|
||||
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> Result<Self> {
|
||||
let mut options: PyModuleOptions = PyModuleOptions { name: None };
|
||||
let mut options: PyModuleOptions = Default::default();
|
||||
|
||||
for option in take_pyo3_options(attrs)? {
|
||||
match option {
|
||||
PyModulePyO3Option::Name(name) => options.set_name(name.0)?,
|
||||
PyModulePyO3Option::PyO3Path(path) => options.set_pyo3_path(path)?,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,12 +48,23 @@ impl PyModuleOptions {
|
|||
self.name = Some(name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_pyo3_path(&mut self, path: PyO3PathAttribute) -> Result<()> {
|
||||
ensure_spanned!(
|
||||
self.pyo3_path.is_none(),
|
||||
path.0.span() => "`pyo3_path` may only be specified once"
|
||||
);
|
||||
|
||||
self.pyo3_path = Some(path);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the function that is called by the python interpreter to initialize the native
|
||||
/// module
|
||||
pub fn py_init(fnname: &Ident, options: PyModuleOptions, doc: PythonDoc) -> TokenStream {
|
||||
let name = options.name.unwrap_or_else(|| fnname.unraw());
|
||||
let pyo3_path = get_pyo3_path(&options.pyo3_path);
|
||||
let cb_name = Ident::new(&format!("PyInit_{}", name), Span::call_site());
|
||||
|
||||
quote! {
|
||||
|
@ -55,7 +72,8 @@ pub fn py_init(fnname: &Ident, options: PyModuleOptions, doc: PythonDoc) -> Toke
|
|||
#[allow(non_snake_case)]
|
||||
/// This autogenerated function is called by the python interpreter when importing
|
||||
/// the module.
|
||||
pub unsafe extern "C" fn #cb_name() -> *mut _pyo3::ffi::PyObject {
|
||||
pub unsafe extern "C" fn #cb_name() -> *mut #pyo3_path::ffi::PyObject {
|
||||
use #pyo3_path as _pyo3;
|
||||
use _pyo3::derive_utils::ModuleDef;
|
||||
static NAME: &str = concat!(stringify!(#name), "\0");
|
||||
static DOC: &str = #doc;
|
||||
|
@ -143,6 +161,7 @@ fn get_pyfn_attr(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Option<PyFnArgs
|
|||
}
|
||||
|
||||
enum PyModulePyO3Option {
|
||||
PyO3Path(PyO3PathAttribute),
|
||||
Name(NameAttribute),
|
||||
}
|
||||
|
||||
|
@ -151,6 +170,8 @@ impl Parse for PyModulePyO3Option {
|
|||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(attributes::kw::name) {
|
||||
input.parse().map(PyModulePyO3Option::Name)
|
||||
} else if lookahead.peek(attributes::kw::pyo3_path) {
|
||||
input.parse().map(PyModulePyO3Option::PyO3Path)
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
|
||||
use crate::attributes::{self, take_pyo3_options, NameAttribute, TextSignatureAttribute};
|
||||
use crate::attributes::{
|
||||
self, take_pyo3_options, NameAttribute, PyO3PathAttribute, TextSignatureAttribute,
|
||||
};
|
||||
use crate::deprecations::Deprecations;
|
||||
use crate::konst::{ConstAttributes, ConstSpec};
|
||||
use crate::pyimpl::{gen_default_slot_impls, gen_py_const, PyClassMethodsType};
|
||||
use crate::pymethod::{impl_py_getter_def, impl_py_setter_def, PropertyType};
|
||||
use crate::utils::{self, unwrap_group, PythonDoc};
|
||||
use crate::utils::{self, get_pyo3_path, unwrap_group, PythonDoc};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::quote;
|
||||
use syn::ext::IdentExt;
|
||||
|
@ -186,10 +188,12 @@ impl PyClassArgs {
|
|||
pub struct PyClassPyO3Options {
|
||||
pub text_signature: Option<TextSignatureAttribute>,
|
||||
pub deprecations: Deprecations,
|
||||
pub pyo3_path: Option<PyO3PathAttribute>,
|
||||
}
|
||||
|
||||
enum PyClassPyO3Option {
|
||||
TextSignature(TextSignatureAttribute),
|
||||
PyO3Path(PyO3PathAttribute),
|
||||
}
|
||||
|
||||
impl Parse for PyClassPyO3Option {
|
||||
|
@ -197,6 +201,8 @@ impl Parse for PyClassPyO3Option {
|
|||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(attributes::kw::text_signature) {
|
||||
input.parse().map(PyClassPyO3Option::TextSignature)
|
||||
} else if lookahead.peek(attributes::kw::pyo3_path) {
|
||||
input.parse().map(PyClassPyO3Option::PyO3Path)
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
|
@ -211,6 +217,9 @@ impl PyClassPyO3Options {
|
|||
PyClassPyO3Option::TextSignature(text_signature) => {
|
||||
options.set_text_signature(text_signature)?;
|
||||
}
|
||||
PyClassPyO3Option::PyO3Path(path) => {
|
||||
options.set_pyo3_path(path)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(options)
|
||||
|
@ -227,6 +236,15 @@ impl PyClassPyO3Options {
|
|||
self.text_signature = Some(text_signature);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_pyo3_path(&mut self, path: PyO3PathAttribute) -> syn::Result<()> {
|
||||
ensure_spanned!(
|
||||
self.pyo3_path.is_none(),
|
||||
path.0.span() => "`text_signature` may only be specified once"
|
||||
);
|
||||
self.pyo3_path = Some(path);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_py_class(
|
||||
|
@ -242,6 +260,7 @@ pub fn build_py_class(
|
|||
.as_ref()
|
||||
.map(|attr| (get_class_python_name(&class.ident, args), attr)),
|
||||
);
|
||||
let pyo3_path = get_pyo3_path(&options.pyo3_path);
|
||||
|
||||
ensure_spanned!(
|
||||
class.generics.params.is_empty(),
|
||||
|
@ -278,6 +297,7 @@ pub fn build_py_class(
|
|||
field_options,
|
||||
methods_type,
|
||||
options.deprecations,
|
||||
pyo3_path,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -358,6 +378,7 @@ fn impl_class(
|
|||
field_options: Vec<(&syn::Field, FieldPyO3Options)>,
|
||||
methods_type: PyClassMethodsType,
|
||||
deprecations: Deprecations,
|
||||
pyo3_path: syn::Path,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let pytypeinfo_impl = impl_pytypeinfo(cls, attr, Some(&deprecations));
|
||||
|
||||
|
@ -368,11 +389,15 @@ fn impl_class(
|
|||
let descriptors = impl_descriptors(cls, field_options)?;
|
||||
|
||||
Ok(quote! {
|
||||
#pytypeinfo_impl
|
||||
const _: () = {
|
||||
use #pyo3_path as _pyo3;
|
||||
|
||||
#py_class_impl
|
||||
#pytypeinfo_impl
|
||||
|
||||
#descriptors
|
||||
#py_class_impl
|
||||
|
||||
#descriptors
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -382,10 +407,12 @@ struct PyClassEnumVariant<'a> {
|
|||
}
|
||||
|
||||
pub fn build_py_enum(
|
||||
enum_: &syn::ItemEnum,
|
||||
args: PyClassArgs,
|
||||
enum_: &mut syn::ItemEnum,
|
||||
args: &PyClassArgs,
|
||||
method_type: PyClassMethodsType,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let options = PyClassPyO3Options::take_pyo3_options(&mut enum_.attrs)?;
|
||||
|
||||
if enum_.variants.is_empty() {
|
||||
bail_spanned!(enum_.brace_token.span => "Empty enums can't be #[pyclass].");
|
||||
}
|
||||
|
@ -394,33 +421,38 @@ pub fn build_py_enum(
|
|||
.iter()
|
||||
.map(extract_variant_data)
|
||||
.collect::<syn::Result<_>>()?;
|
||||
impl_enum(enum_, args, variants, method_type)
|
||||
impl_enum(enum_, args, variants, method_type, options)
|
||||
}
|
||||
|
||||
fn impl_enum(
|
||||
enum_: &syn::ItemEnum,
|
||||
attrs: PyClassArgs,
|
||||
args: &PyClassArgs,
|
||||
variants: Vec<PyClassEnumVariant>,
|
||||
methods_type: PyClassMethodsType,
|
||||
options: PyClassPyO3Options,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let enum_name = &enum_.ident;
|
||||
let doc = utils::get_doc(&enum_.attrs, None);
|
||||
let enum_cls = impl_enum_class(enum_name, &attrs, variants, doc, methods_type)?;
|
||||
|
||||
Ok(quote! {
|
||||
#enum_cls
|
||||
})
|
||||
let doc = utils::get_doc(
|
||||
&enum_.attrs,
|
||||
options
|
||||
.text_signature
|
||||
.as_ref()
|
||||
.map(|attr| (get_class_python_name(&enum_.ident, args), attr)),
|
||||
);
|
||||
let pyo3_path = get_pyo3_path(&options.pyo3_path);
|
||||
impl_enum_class(enum_name, args, variants, doc, methods_type, pyo3_path)
|
||||
}
|
||||
|
||||
fn impl_enum_class(
|
||||
cls: &syn::Ident,
|
||||
attr: &PyClassArgs,
|
||||
args: &PyClassArgs,
|
||||
variants: Vec<PyClassEnumVariant>,
|
||||
doc: PythonDoc,
|
||||
methods_type: PyClassMethodsType,
|
||||
pyo3_path: syn::Path,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let pytypeinfo = impl_pytypeinfo(cls, attr, None);
|
||||
let pyclass_impls = PyClassImplsBuilder::new(cls, attr, methods_type)
|
||||
let pytypeinfo = impl_pytypeinfo(cls, args, None);
|
||||
let pyclass_impls = PyClassImplsBuilder::new(cls, args, methods_type)
|
||||
.doc(doc)
|
||||
.impl_all();
|
||||
let descriptors = unit_variants_as_descriptors(cls, variants.iter().map(|v| v.ident));
|
||||
|
@ -447,15 +479,17 @@ fn impl_enum_class(
|
|||
|
||||
let default_impls = gen_default_slot_impls(cls, vec![default_repr_impl]);
|
||||
Ok(quote! {
|
||||
const _: () = {
|
||||
use #pyo3_path as _pyo3;
|
||||
|
||||
#pytypeinfo
|
||||
#pytypeinfo
|
||||
|
||||
#pyclass_impls
|
||||
#pyclass_impls
|
||||
|
||||
#descriptors
|
||||
|
||||
#default_impls
|
||||
#default_impls
|
||||
|
||||
#descriptors
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
use crate::{
|
||||
attributes::{
|
||||
self, get_pyo3_options, take_attributes, take_pyo3_options, FromPyWithAttribute,
|
||||
NameAttribute, TextSignatureAttribute,
|
||||
NameAttribute, PyO3PathAttribute, TextSignatureAttribute,
|
||||
},
|
||||
deprecations::Deprecations,
|
||||
method::{self, CallingConvention, FnArg},
|
||||
pymethod::check_generic,
|
||||
utils::{self, ensure_not_async_fn},
|
||||
utils::{self, ensure_not_async_fn, get_pyo3_path},
|
||||
};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
|
@ -239,17 +239,12 @@ pub struct PyFunctionOptions {
|
|||
pub signature: Option<PyFunctionSignature>,
|
||||
pub text_signature: Option<TextSignatureAttribute>,
|
||||
pub deprecations: Deprecations,
|
||||
pub pyo3_path: Option<PyO3PathAttribute>,
|
||||
}
|
||||
|
||||
impl Parse for PyFunctionOptions {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let mut options = PyFunctionOptions {
|
||||
pass_module: None,
|
||||
name: None,
|
||||
signature: None,
|
||||
text_signature: None,
|
||||
deprecations: Deprecations::new(),
|
||||
};
|
||||
let mut options = PyFunctionOptions::default();
|
||||
|
||||
while !input.is_empty() {
|
||||
let lookahead = input.lookahead1();
|
||||
|
@ -262,6 +257,9 @@ impl Parse for PyFunctionOptions {
|
|||
if !input.is_empty() {
|
||||
let _: Comma = input.parse()?;
|
||||
}
|
||||
} else if lookahead.peek(attributes::kw::pyo3_path) {
|
||||
// TODO needs duplicate check?
|
||||
options.pyo3_path = Some(input.parse()?);
|
||||
} else {
|
||||
// If not recognised attribute, this is "legacy" pyfunction syntax #[pyfunction(a, b)]
|
||||
//
|
||||
|
@ -280,6 +278,7 @@ pub enum PyFunctionOption {
|
|||
PassModule(attributes::kw::pass_module),
|
||||
Signature(PyFunctionSignature),
|
||||
TextSignature(TextSignatureAttribute),
|
||||
PyO3Path(PyO3PathAttribute),
|
||||
}
|
||||
|
||||
impl Parse for PyFunctionOption {
|
||||
|
@ -293,6 +292,8 @@ impl Parse for PyFunctionOption {
|
|||
input.parse().map(PyFunctionOption::Signature)
|
||||
} else if lookahead.peek(attributes::kw::text_signature) {
|
||||
input.parse().map(PyFunctionOption::TextSignature)
|
||||
} else if lookahead.peek(attributes::kw::pyo3_path) {
|
||||
input.parse().map(PyFunctionOption::PyO3Path)
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
|
@ -335,6 +336,13 @@ impl PyFunctionOptions {
|
|||
);
|
||||
self.text_signature = Some(text_signature);
|
||||
}
|
||||
PyFunctionOption::PyO3Path(path) => {
|
||||
ensure_spanned!(
|
||||
self.pyo3_path.is_none(),
|
||||
path.0.span() => "`pyo3_path` may only be specified once"
|
||||
);
|
||||
self.pyo3_path = Some(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -410,6 +418,7 @@ pub fn impl_wrap_pyfunction(
|
|||
);
|
||||
|
||||
let function_wrapper_ident = function_wrapper_ident(&func.sig.ident);
|
||||
let pyo3_path = get_pyo3_path(&options.pyo3_path);
|
||||
|
||||
let spec = method::FnSpec {
|
||||
tp: if options.pass_module.is_some() {
|
||||
|
@ -426,6 +435,7 @@ pub fn impl_wrap_pyfunction(
|
|||
doc,
|
||||
deprecations: options.deprecations,
|
||||
text_signature: options.text_signature,
|
||||
pyo3_path: pyo3_path.clone(),
|
||||
};
|
||||
|
||||
let wrapper_ident = format_ident!("__pyo3_raw_{}", spec.name);
|
||||
|
@ -434,9 +444,11 @@ pub fn impl_wrap_pyfunction(
|
|||
|
||||
let wrapped_pyfunction = quote! {
|
||||
#wrapper
|
||||
|
||||
pub(crate) fn #function_wrapper_ident<'a>(
|
||||
args: impl ::std::convert::Into<_pyo3::derive_utils::PyFunctionArguments<'a>>
|
||||
) -> _pyo3::PyResult<&'a _pyo3::types::PyCFunction> {
|
||||
args: impl ::std::convert::Into<#pyo3_path::derive_utils::PyFunctionArguments<'a>>
|
||||
) -> #pyo3_path::PyResult<&'a #pyo3_path::types::PyCFunction> {
|
||||
use #pyo3_path as _pyo3;
|
||||
_pyo3::types::PyCFunction::internal_new(#methoddef, args.into())
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,14 +3,20 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use crate::{
|
||||
attributes::{self, take_pyo3_options, PyO3PathAttribute},
|
||||
konst::{ConstAttributes, ConstSpec},
|
||||
pyfunction::PyFunctionOptions,
|
||||
pymethod::{self, is_proto_method},
|
||||
utils::get_pyo3_path,
|
||||
};
|
||||
use proc_macro2::TokenStream;
|
||||
use pymethod::GeneratedPyMethod;
|
||||
use quote::quote;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
spanned::Spanned,
|
||||
Result,
|
||||
};
|
||||
|
||||
/// The mechanism used to collect `#[pymethods]` into the type object
|
||||
#[derive(Copy, Clone)]
|
||||
|
@ -19,6 +25,50 @@ pub enum PyClassMethodsType {
|
|||
Inventory,
|
||||
}
|
||||
|
||||
enum PyImplPyO3Option {
|
||||
PyO3Path(PyO3PathAttribute),
|
||||
}
|
||||
|
||||
impl Parse for PyImplPyO3Option {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(attributes::kw::pyo3_path) {
|
||||
input.parse().map(PyImplPyO3Option::PyO3Path)
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PyImplOptions {
|
||||
pyo3_path: Option<PyO3PathAttribute>,
|
||||
}
|
||||
|
||||
impl PyImplOptions {
|
||||
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> Result<Self> {
|
||||
let mut options: PyImplOptions = Default::default();
|
||||
|
||||
for option in take_pyo3_options(attrs)? {
|
||||
match option {
|
||||
PyImplPyO3Option::PyO3Path(path) => options.set_pyo3_path(path)?,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(options)
|
||||
}
|
||||
|
||||
fn set_pyo3_path(&mut self, path: PyO3PathAttribute) -> Result<()> {
|
||||
ensure_spanned!(
|
||||
self.pyo3_path.is_none(),
|
||||
path.0.span() => "`pyo3_path` may only be specified once"
|
||||
);
|
||||
|
||||
self.pyo3_path = Some(path);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_py_methods(
|
||||
ast: &mut syn::ItemImpl,
|
||||
methods_type: PyClassMethodsType,
|
||||
|
@ -31,7 +81,8 @@ pub fn build_py_methods(
|
|||
"#[pymethods] cannot be used with lifetime parameters or generics"
|
||||
);
|
||||
} else {
|
||||
impl_methods(&ast.self_ty, &mut ast.items, methods_type)
|
||||
let options = PyImplOptions::from_attrs(&mut ast.attrs)?;
|
||||
impl_methods(&ast.self_ty, &mut ast.items, methods_type, options)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,6 +90,7 @@ pub fn impl_methods(
|
|||
ty: &syn::Type,
|
||||
impls: &mut Vec<syn::ImplItem>,
|
||||
methods_type: PyClassMethodsType,
|
||||
options: PyImplOptions,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let mut trait_impls = Vec::new();
|
||||
let mut proto_impls = Vec::new();
|
||||
|
@ -49,8 +101,9 @@ pub fn impl_methods(
|
|||
for iimpl in impls.iter_mut() {
|
||||
match iimpl {
|
||||
syn::ImplItem::Method(meth) => {
|
||||
let options = PyFunctionOptions::from_attrs(&mut meth.attrs)?;
|
||||
match pymethod::gen_py_method(ty, &mut meth.sig, &mut meth.attrs, options)? {
|
||||
let mut fun_options = PyFunctionOptions::from_attrs(&mut meth.attrs)?;
|
||||
fun_options.pyo3_path = fun_options.pyo3_path.or_else(|| options.pyo3_path.clone());
|
||||
match pymethod::gen_py_method(ty, &mut meth.sig, &mut meth.attrs, fun_options)? {
|
||||
GeneratedPyMethod::Method(token_stream) => {
|
||||
let attrs = get_cfg_attributes(&meth.attrs);
|
||||
methods.push(quote!(#(#attrs)* #token_stream));
|
||||
|
@ -95,25 +148,35 @@ pub fn impl_methods(
|
|||
|
||||
add_shared_proto_slots(ty, &mut proto_impls, implemented_proto_fragments);
|
||||
|
||||
let pyo3_path = get_pyo3_path(&options.pyo3_path);
|
||||
|
||||
Ok(match methods_type {
|
||||
PyClassMethodsType::Specialization => {
|
||||
let methods_registration = impl_py_methods(ty, methods);
|
||||
let protos_registration = impl_protos(ty, proto_impls);
|
||||
|
||||
quote! {
|
||||
#(#trait_impls)*
|
||||
const _: () = {
|
||||
use #pyo3_path as _pyo3;
|
||||
|
||||
#protos_registration
|
||||
#(#trait_impls)*
|
||||
|
||||
#methods_registration
|
||||
#protos_registration
|
||||
|
||||
#methods_registration
|
||||
};
|
||||
}
|
||||
}
|
||||
PyClassMethodsType::Inventory => {
|
||||
let inventory = submit_methods_inventory(ty, methods, proto_impls);
|
||||
quote! {
|
||||
#(#trait_impls)*
|
||||
const _: () = {
|
||||
use #pyo3_path as _pyo3;
|
||||
|
||||
#inventory
|
||||
#(#trait_impls)*
|
||||
|
||||
#inventory
|
||||
};
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -86,10 +86,14 @@ fn impl_proto_impl(
|
|||
}
|
||||
let normal_methods = impl_normal_methods(py_methods, ty, proto);
|
||||
let protocol_methods = impl_proto_methods(method_names, ty, proto);
|
||||
|
||||
Ok(quote! {
|
||||
#trait_impls
|
||||
#normal_methods
|
||||
#protocol_methods
|
||||
const _: () = {
|
||||
use ::pyo3 as _pyo3; // pyproto doesn't support specifying #[pyo3(pyo3_path)]
|
||||
#trait_impls
|
||||
#normal_methods
|
||||
#protocol_methods
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ use proc_macro2::{Span, TokenStream};
|
|||
use quote::ToTokens;
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
use crate::attributes::TextSignatureAttribute;
|
||||
use crate::attributes::{PyO3PathAttribute, TextSignatureAttribute};
|
||||
|
||||
/// Macro inspired by `anyhow::anyhow!` to create a compiler error with the given span.
|
||||
macro_rules! err_spanned {
|
||||
|
@ -189,3 +189,10 @@ pub(crate) fn replace_self(ty: &mut syn::Type, cls: &syn::Type) {
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the path to the pyo3 crate, or use the default (`::pyo3`).
|
||||
pub(crate) fn get_pyo3_path(attr: &Option<PyO3PathAttribute>) -> syn::Path {
|
||||
attr.as_ref()
|
||||
.map(|p| p.0.clone())
|
||||
.unwrap_or_else(|| syn::parse_str("::pyo3").unwrap())
|
||||
}
|
||||
|
|
|
@ -216,16 +216,16 @@ fn pyclass_impl(
|
|||
}
|
||||
|
||||
fn pyclass_enum_impl(
|
||||
attr: TokenStream,
|
||||
enum_: syn::ItemEnum,
|
||||
attrs: TokenStream,
|
||||
mut ast: syn::ItemEnum,
|
||||
methods_type: PyClassMethodsType,
|
||||
) -> TokenStream {
|
||||
let args = parse_macro_input!(attr with PyClassArgs::parse_enum_args);
|
||||
let args = parse_macro_input!(attrs with PyClassArgs::parse_enum_args);
|
||||
let expanded =
|
||||
build_py_enum(&enum_, args, methods_type).unwrap_or_else(|e| e.into_compile_error());
|
||||
build_py_enum(&mut ast, &args, methods_type).unwrap_or_else(|e| e.into_compile_error());
|
||||
|
||||
quote!(
|
||||
#enum_
|
||||
#ast
|
||||
#expanded
|
||||
)
|
||||
.into()
|
||||
|
|
|
@ -132,7 +132,7 @@ error: only one of `attribute` or `item` can be provided
|
|||
118 | #[pyo3(item, attribute)]
|
||||
| ^
|
||||
|
||||
error: expected `transparent` or `annotation`
|
||||
error: expected one of: `transparent`, `annotation`, `pyo3_path`
|
||||
--> tests/ui/invalid_frompy_derive.rs:123:8
|
||||
|
|
||||
123 | #[pyo3(unknown = "should not work")]
|
||||
|
|
Loading…
Reference in a new issue