Skip to content

Commit 515eb0d

Browse files
committed
pyo3_path, part 2: add pyo3_path options and use them.
1 parent 997ceed commit 515eb0d

File tree

13 files changed

+291
-81
lines changed

13 files changed

+291
-81
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2121

2222
- Add `Py::setattr` method. [#2009](https://github.com/PyO3/pyo3/pull/2009)
2323
- Add `PyCapsule`, exposing the [Capsule API](https://docs.python.org/3/c-api/capsule.html#capsules). [#1980](https://github.com/PyO3/pyo3/pull/1980)
24+
- All PyO3 proc-macros except the deprecated `#[pyproto]` now accept a supplemental attribute `#[pyo3(pyo3_path = "some::path")]` that specifies
25+
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)
2426

2527
### Changed
2628

guide/src/faq.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,23 @@ a: <builtins.Inner object at 0x0000020044FCC670>
139139
b: <builtins.Inner object at 0x0000020044FCC670>
140140
```
141141
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.
142+
143+
## I want to use the `pyo3` crate re-exported from from dependency but the proc-macros fail!
144+
145+
All PyO3 proc-macros (`#[pyclass]`, `#[pyfunction]`, `#[derive(FromPyObject)]`
146+
and so on) expect the `pyo3` crate to be available under that name in your crate
147+
root, which is the normal situation when `pyo3` is a direct dependency of your
148+
crate.
149+
150+
However, when the dependency is renamed, or your crate only indirectly depends
151+
on `pyo3`, you need to let the macro code know where to find the crate. This is
152+
done with the `pyo3_path` attribute:
153+
154+
```rust
155+
# use pyo3::prelude::*;
156+
# pub extern crate pyo3;
157+
# mod reexported { pub use ::pyo3; }
158+
#[pyclass]
159+
#[pyo3(pyo3_path = "reexported::pyo3")]
160+
struct MyClass;
161+
```

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: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use crate::attributes::{self, get_pyo3_options, FromPyWithAttribute};
1+
use crate::{
2+
attributes::{self, get_pyo3_options, FromPyWithAttribute, PyO3PathAttribute},
3+
utils::get_pyo3_path,
4+
};
25
use proc_macro2::TokenStream;
36
use quote::quote;
47
use syn::{
@@ -300,20 +303,25 @@ impl<'a> Container<'a> {
300303
}
301304
}
302305

306+
#[derive(Default)]
303307
struct ContainerOptions {
304308
/// Treat the Container as a Wrapper, directly extract its fields from the input object.
305309
transparent: bool,
306310
/// Change the name of an enum variant in the generated error message.
307311
annotation: Option<syn::LitStr>,
312+
/// Change the path for the pyo3 crate
313+
pyo3_path: Option<PyO3PathAttribute>,
308314
}
309315

310316
/// Attributes for deriving FromPyObject scoped on containers.
311-
#[derive(Clone, Debug, PartialEq)]
317+
#[derive(Debug)]
312318
enum ContainerPyO3Attribute {
313319
/// Treat the Container as a Wrapper, directly extract its fields from the input object.
314320
Transparent(attributes::kw::transparent),
315321
/// Change the name of an enum variant in the generated error message.
316322
ErrorAnnotation(LitStr),
323+
/// Change the path for the pyo3 crate
324+
PyO3Path(PyO3PathAttribute),
317325
}
318326

319327
impl Parse for ContainerPyO3Attribute {
@@ -326,6 +334,8 @@ impl Parse for ContainerPyO3Attribute {
326334
let _: attributes::kw::annotation = input.parse()?;
327335
let _: Token![=] = input.parse()?;
328336
input.parse().map(ContainerPyO3Attribute::ErrorAnnotation)
337+
} else if lookahead.peek(attributes::kw::pyo3_path) {
338+
input.parse().map(ContainerPyO3Attribute::PyO3Path)
329339
} else {
330340
Err(lookahead.error())
331341
}
@@ -334,10 +344,8 @@ impl Parse for ContainerPyO3Attribute {
334344

335345
impl ContainerOptions {
336346
fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
337-
let mut options = ContainerOptions {
338-
transparent: false,
339-
annotation: None,
340-
};
347+
let mut options = ContainerOptions::default();
348+
341349
for attr in attrs {
342350
if let Some(pyo3_attrs) = get_pyo3_options(attr)? {
343351
for pyo3_attr in pyo3_attrs {
@@ -356,6 +364,13 @@ impl ContainerOptions {
356364
);
357365
options.annotation = Some(lit_str);
358366
}
367+
ContainerPyO3Attribute::PyO3Path(path) => {
368+
ensure_spanned!(
369+
options.pyo3_path.is_none(),
370+
path.0.span() => "`pyo3_path` may only be provided once"
371+
);
372+
options.pyo3_path = Some(path);
373+
}
359374
}
360375
}
361376
}
@@ -499,13 +514,18 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
499514
.predicates
500515
.push(parse_quote!(#gen_ident: FromPyObject<#lt_param>))
501516
}
517+
let options = ContainerOptions::from_attrs(&tokens.attrs)?;
518+
let pyo3_path = get_pyo3_path(&options.pyo3_path);
502519
let derives = match &tokens.data {
503520
syn::Data::Enum(en) => {
521+
if options.transparent || options.annotation.is_some() {
522+
bail_spanned!(tokens.span() => "`transparent` or `annotation` is not supported \
523+
at top level for enums");
524+
}
504525
let en = Enum::new(en, &tokens.ident)?;
505526
en.build()
506527
}
507528
syn::Data::Struct(st) => {
508-
let options = ContainerOptions::from_attrs(&tokens.attrs)?;
509529
if let Some(lit_str) = &options.annotation {
510530
bail_spanned!(lit_str.span() => "`annotation` is unsupported for structs");
511531
}
@@ -520,11 +540,15 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
520540

521541
let ident = &tokens.ident;
522542
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
543+
const _: () = {
544+
use #pyo3_path as _pyo3;
545+
546+
#[automatically_derived]
547+
impl#trait_generics _pyo3::FromPyObject<#lt_param> for #ident#generics #where_clause {
548+
fn extract(obj: &#lt_param _pyo3::PyAny) -> _pyo3::PyResult<Self> {
549+
#derives
550+
}
527551
}
528-
}
552+
};
529553
))
530554
}

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)