Skip to content

Commit a695f68

Browse files
committed
pyo3_path, part 2: add pyo3_path options and use them.
1 parent b8e4467 commit a695f68

File tree

11 files changed

+301
-83
lines changed

11 files changed

+301
-83
lines changed

pyo3-macros-backend/src/attributes.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use syn::{
22
parse::{Parse, ParseStream},
33
punctuated::Punctuated,
44
token::Comma,
5-
Attribute, ExprPath, Ident, LitStr, Result, Token,
5+
Attribute, ExprPath, Ident, LitStr, Path, Result, Token,
66
};
77

88
pub mod kw {
@@ -17,6 +17,7 @@ pub mod kw {
1717
syn::custom_keyword!(signature);
1818
syn::custom_keyword!(text_signature);
1919
syn::custom_keyword!(transparent);
20+
syn::custom_keyword!(pyo3_path);
2021
}
2122

2223
#[derive(Clone, Debug, PartialEq)]
@@ -43,6 +44,19 @@ impl Parse for NameAttribute {
4344
}
4445
}
4546

47+
/// For specifying the path to the pyo3 crate.
48+
#[derive(Clone, Debug, PartialEq)]
49+
pub struct PyO3PathAttribute(pub Path);
50+
51+
impl Parse for PyO3PathAttribute {
52+
fn parse(input: ParseStream) -> Result<Self> {
53+
let _: kw::pyo3_path = input.parse()?;
54+
let _: Token![=] = input.parse()?;
55+
let string_literal: LitStr = input.parse()?;
56+
string_literal.parse().map(PyO3PathAttribute)
57+
}
58+
}
59+
4660
#[derive(Clone, Debug, PartialEq)]
4761
pub struct TextSignatureAttribute {
4862
pub kw: kw::text_signature,

pyo3-macros-backend/src/from_pyobject.rs

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
use crate::attributes::{self, get_pyo3_options, FromPyWithAttribute};
2-
use proc_macro2::TokenStream;
1+
use crate::{
2+
attributes::{self, get_pyo3_options, FromPyWithAttribute, PyO3PathAttribute},
3+
utils::get_pyo3_path,
4+
};
5+
use proc_macro2::{Span, TokenStream};
36
use quote::quote;
47
use syn::{
8+
ext::IdentExt,
59
parenthesized,
610
parse::{Parse, ParseStream},
711
parse_quote,
@@ -300,20 +304,25 @@ impl<'a> Container<'a> {
300304
}
301305
}
302306

307+
#[derive(Default)]
303308
struct ContainerOptions {
304309
/// Treat the Container as a Wrapper, directly extract its fields from the input object.
305310
transparent: bool,
306311
/// Change the name of an enum variant in the generated error message.
307312
annotation: Option<syn::LitStr>,
313+
/// Change the path for the pyo3 crate
314+
pyo3_path: Option<PyO3PathAttribute>,
308315
}
309316

310317
/// Attributes for deriving FromPyObject scoped on containers.
311-
#[derive(Clone, Debug, PartialEq)]
318+
#[derive(Debug)]
312319
enum ContainerPyO3Attribute {
313320
/// Treat the Container as a Wrapper, directly extract its fields from the input object.
314321
Transparent(attributes::kw::transparent),
315322
/// Change the name of an enum variant in the generated error message.
316323
ErrorAnnotation(LitStr),
324+
/// Change the path for the pyo3 crate
325+
PyO3Path(PyO3PathAttribute),
317326
}
318327

319328
impl Parse for ContainerPyO3Attribute {
@@ -326,6 +335,8 @@ impl Parse for ContainerPyO3Attribute {
326335
let _: attributes::kw::annotation = input.parse()?;
327336
let _: Token![=] = input.parse()?;
328337
input.parse().map(ContainerPyO3Attribute::ErrorAnnotation)
338+
} else if lookahead.peek(attributes::kw::pyo3_path) {
339+
input.parse().map(ContainerPyO3Attribute::PyO3Path)
329340
} else {
330341
Err(lookahead.error())
331342
}
@@ -334,10 +345,8 @@ impl Parse for ContainerPyO3Attribute {
334345

335346
impl ContainerOptions {
336347
fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
337-
let mut options = ContainerOptions {
338-
transparent: false,
339-
annotation: None,
340-
};
348+
let mut options = ContainerOptions::default();
349+
341350
for attr in attrs {
342351
if let Some(pyo3_attrs) = get_pyo3_options(attr)? {
343352
for pyo3_attr in pyo3_attrs {
@@ -356,6 +365,13 @@ impl ContainerOptions {
356365
);
357366
options.annotation = Some(lit_str);
358367
}
368+
ContainerPyO3Attribute::PyO3Path(path) => {
369+
ensure_spanned!(
370+
options.pyo3_path.is_none(),
371+
path.0.span() => "`pyo3_path` may only be provided once"
372+
);
373+
options.pyo3_path = Some(path);
374+
}
359375
}
360376
}
361377
}
@@ -499,13 +515,18 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
499515
.predicates
500516
.push(parse_quote!(#gen_ident: FromPyObject<#lt_param>))
501517
}
518+
let options = ContainerOptions::from_attrs(&tokens.attrs)?;
519+
let pyo3_path = get_pyo3_path(&options.pyo3_path);
502520
let derives = match &tokens.data {
503521
syn::Data::Enum(en) => {
522+
if options.transparent || options.annotation.is_some() {
523+
bail_spanned!(tokens.span() => "`transparent` or `annotation` is not supported \
524+
at top level for enums");
525+
}
504526
let en = Enum::new(en, &tokens.ident)?;
505527
en.build()
506528
}
507529
syn::Data::Struct(st) => {
508-
let options = ContainerOptions::from_attrs(&tokens.attrs)?;
509530
if let Some(lit_str) = &options.annotation {
510531
bail_spanned!(lit_str.span() => "`annotation` is unsupported for structs");
511532
}
@@ -518,13 +539,23 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
518539
),
519540
};
520541

542+
let const_name = syn::Ident::new(
543+
&format!("_PYO3__FROMPYOBJECT_FOR_{}", tokens.ident.unraw()),
544+
Span::call_site(),
545+
);
546+
521547
let ident = &tokens.ident;
548+
522549
Ok(quote!(
523-
#[automatically_derived]
524-
impl#trait_generics _pyo3::FromPyObject<#lt_param> for #ident#generics #where_clause {
525-
fn extract(obj: &#lt_param _pyo3::PyAny) -> _pyo3::PyResult<Self> {
526-
#derives
550+
const #const_name: () = {
551+
use #pyo3_path as _pyo3;
552+
553+
#[automatically_derived]
554+
impl#trait_generics _pyo3::FromPyObject<#lt_param> for #ident#generics #where_clause {
555+
fn extract(obj: &#lt_param _pyo3::PyAny) -> _pyo3::PyResult<Self> {
556+
#derives
557+
}
527558
}
528-
}
559+
};
529560
))
530561
}

pyo3-macros-backend/src/method.rs

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::deprecations::Deprecation;
55
use crate::params::{accept_args_kwargs, impl_arg_params};
66
use crate::pyfunction::PyFunctionOptions;
77
use crate::pyfunction::{PyFunctionArgPyO3Attributes, PyFunctionSignature};
8-
use crate::utils::{self, PythonDoc};
8+
use crate::utils::{self, get_pyo3_path, PythonDoc};
99
use crate::{deprecations::Deprecations, pyfunction::Argument};
1010
use proc_macro2::{Span, TokenStream};
1111
use quote::ToTokens;
@@ -228,6 +228,7 @@ pub struct FnSpec<'a> {
228228
pub deprecations: Deprecations,
229229
pub convention: CallingConvention,
230230
pub text_signature: Option<TextSignatureAttribute>,
231+
pub pyo3_path: syn::Path,
231232
}
232233

233234
pub fn get_return_info(output: &syn::ReturnType) -> syn::Type {
@@ -254,12 +255,14 @@ pub fn parse_method_receiver(arg: &syn::FnArg) -> Result<SelfType> {
254255
impl<'a> FnSpec<'a> {
255256
/// Parser function signature and function attributes
256257
pub fn parse(
258+
// Signature is mutable to remove the `Python` argument.
257259
sig: &'a mut syn::Signature,
258260
meth_attrs: &mut Vec<syn::Attribute>,
259261
options: PyFunctionOptions,
260262
) -> Result<FnSpec<'a>> {
261263
let PyFunctionOptions {
262264
text_signature,
265+
pyo3_path,
263266
name,
264267
mut deprecations,
265268
..
@@ -278,6 +281,7 @@ impl<'a> FnSpec<'a> {
278281
let name = &sig.ident;
279282
let ty = get_return_info(&sig.output);
280283
let python_name = python_name.as_ref().unwrap_or(name).unraw();
284+
let pyo3_path = get_pyo3_path(&pyo3_path);
281285

282286
let doc = utils::get_doc(
283287
meth_attrs,
@@ -311,6 +315,7 @@ impl<'a> FnSpec<'a> {
311315
doc,
312316
deprecations,
313317
text_signature,
318+
pyo3_path,
314319
})
315320
}
316321

@@ -472,14 +477,16 @@ impl<'a> FnSpec<'a> {
472477
};
473478
let rust_call =
474479
quote! { _pyo3::callback::convert(#py, #rust_name(#self_arg #(#arg_names),*)) };
480+
let pyo3_path = &self.pyo3_path;
475481
Ok(match self.convention {
476482
CallingConvention::Noargs => {
477483
quote! {
478484
unsafe extern "C" fn #ident (
479-
_slf: *mut _pyo3::ffi::PyObject,
480-
_args: *mut _pyo3::ffi::PyObject,
481-
) -> *mut _pyo3::ffi::PyObject
485+
_slf: *mut #pyo3_path::ffi::PyObject,
486+
_args: *mut #pyo3_path::ffi::PyObject,
487+
) -> *mut #pyo3_path::ffi::PyObject
482488
{
489+
use #pyo3_path as _pyo3;
483490
#deprecations
484491
_pyo3::callback::handle_panic(|#py| {
485492
#self_conversion
@@ -492,11 +499,12 @@ impl<'a> FnSpec<'a> {
492499
let arg_convert_and_rust_call = impl_arg_params(self, cls, rust_call, &py, true)?;
493500
quote! {
494501
unsafe extern "C" fn #ident (
495-
_slf: *mut _pyo3::ffi::PyObject,
496-
_args: *const *mut _pyo3::ffi::PyObject,
497-
_nargs: _pyo3::ffi::Py_ssize_t,
498-
_kwnames: *mut _pyo3::ffi::PyObject) -> *mut _pyo3::ffi::PyObject
502+
_slf: *mut #pyo3_path::ffi::PyObject,
503+
_args: *const *mut #pyo3_path::ffi::PyObject,
504+
_nargs: #pyo3_path::ffi::Py_ssize_t,
505+
_kwnames: *mut #pyo3_path::ffi::PyObject) -> *mut #pyo3_path::ffi::PyObject
499506
{
507+
use #pyo3_path as _pyo3;
500508
#deprecations
501509
_pyo3::callback::handle_panic(|#py| {
502510
#self_conversion
@@ -519,10 +527,11 @@ impl<'a> FnSpec<'a> {
519527
let arg_convert_and_rust_call = impl_arg_params(self, cls, rust_call, &py, false)?;
520528
quote! {
521529
unsafe extern "C" fn #ident (
522-
_slf: *mut _pyo3::ffi::PyObject,
523-
_args: *mut _pyo3::ffi::PyObject,
524-
_kwargs: *mut _pyo3::ffi::PyObject) -> *mut _pyo3::ffi::PyObject
530+
_slf: *mut #pyo3_path::ffi::PyObject,
531+
_args: *mut #pyo3_path::ffi::PyObject,
532+
_kwargs: *mut #pyo3_path::ffi::PyObject) -> *mut #pyo3_path::ffi::PyObject
525533
{
534+
use #pyo3_path as _pyo3;
526535
#deprecations
527536
_pyo3::callback::handle_panic(|#py| {
528537
#self_conversion
@@ -539,10 +548,11 @@ impl<'a> FnSpec<'a> {
539548
let arg_convert_and_rust_call = impl_arg_params(self, cls, rust_call, &py, false)?;
540549
quote! {
541550
unsafe extern "C" fn #ident (
542-
subtype: *mut _pyo3::ffi::PyTypeObject,
543-
_args: *mut _pyo3::ffi::PyObject,
544-
_kwargs: *mut _pyo3::ffi::PyObject) -> *mut _pyo3::ffi::PyObject
551+
subtype: *mut #pyo3_path::ffi::PyTypeObject,
552+
_args: *mut #pyo3_path::ffi::PyObject,
553+
_kwargs: *mut #pyo3_path::ffi::PyObject) -> *mut #pyo3_path::ffi::PyObject
545554
{
555+
use #pyo3_path as _pyo3;
546556
#deprecations
547557
use _pyo3::callback::IntoPyCallbackOutput;
548558
_pyo3::callback::handle_panic(|#py| {

pyo3-macros-backend/src/module.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
//! Code generation for the function that initializes a python module and adds classes and function.
33
44
use crate::{
5-
attributes::{self, is_attribute_ident, take_attributes, take_pyo3_options, NameAttribute},
5+
attributes::{
6+
self, is_attribute_ident, take_attributes, take_pyo3_options, NameAttribute,
7+
PyO3PathAttribute,
8+
},
69
pyfunction::{impl_wrap_pyfunction, PyFunctionOptions},
7-
utils::PythonDoc,
10+
utils::{get_pyo3_path, PythonDoc},
811
};
912
use proc_macro2::{Span, TokenStream};
1013
use quote::quote;
@@ -16,17 +19,20 @@ use syn::{
1619
Ident, Path, Result,
1720
};
1821

22+
#[derive(Default)]
1923
pub struct PyModuleOptions {
24+
pyo3_path: Option<PyO3PathAttribute>,
2025
name: Option<syn::Ident>,
2126
}
2227

2328
impl PyModuleOptions {
2429
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> Result<Self> {
25-
let mut options: PyModuleOptions = PyModuleOptions { name: None };
30+
let mut options: PyModuleOptions = Default::default();
2631

2732
for option in take_pyo3_options(attrs)? {
2833
match option {
2934
PyModulePyO3Option::Name(name) => options.set_name(name.0)?,
35+
PyModulePyO3Option::PyO3Path(path) => options.set_pyo3_path(path)?,
3036
}
3137
}
3238

@@ -42,20 +48,32 @@ impl PyModuleOptions {
4248
self.name = Some(name);
4349
Ok(())
4450
}
51+
52+
fn set_pyo3_path(&mut self, path: PyO3PathAttribute) -> Result<()> {
53+
ensure_spanned!(
54+
self.pyo3_path.is_none(),
55+
path.0.span() => "`pyo3_path` may only be specified once"
56+
);
57+
58+
self.pyo3_path = Some(path);
59+
Ok(())
60+
}
4561
}
4662

4763
/// Generates the function that is called by the python interpreter to initialize the native
4864
/// module
4965
pub fn py_init(fnname: &Ident, options: PyModuleOptions, doc: PythonDoc) -> TokenStream {
5066
let name = options.name.unwrap_or_else(|| fnname.unraw());
67+
let pyo3_path = get_pyo3_path(&options.pyo3_path);
5168
let cb_name = Ident::new(&format!("PyInit_{}", name), Span::call_site());
5269

5370
quote! {
5471
#[no_mangle]
5572
#[allow(non_snake_case)]
5673
/// This autogenerated function is called by the python interpreter when importing
5774
/// the module.
58-
pub unsafe extern "C" fn #cb_name() -> *mut _pyo3::ffi::PyObject {
75+
pub unsafe extern "C" fn #cb_name() -> *mut #pyo3_path::ffi::PyObject {
76+
use #pyo3_path as _pyo3;
5977
use _pyo3::derive_utils::ModuleDef;
6078
static NAME: &str = concat!(stringify!(#name), "\0");
6179
static DOC: &str = #doc;
@@ -143,6 +161,7 @@ fn get_pyfn_attr(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Option<PyFnArgs
143161
}
144162

145163
enum PyModulePyO3Option {
164+
PyO3Path(PyO3PathAttribute),
146165
Name(NameAttribute),
147166
}
148167

@@ -151,6 +170,8 @@ impl Parse for PyModulePyO3Option {
151170
let lookahead = input.lookahead1();
152171
if lookahead.peek(attributes::kw::name) {
153172
input.parse().map(PyModulePyO3Option::Name)
173+
} else if lookahead.peek(attributes::kw::pyo3_path) {
174+
input.parse().map(PyModulePyO3Option::PyO3Path)
154175
} else {
155176
Err(lookahead.error())
156177
}

0 commit comments

Comments
 (0)