Skip to content

Commit 469d72a

Browse files
authored
Merge pull request #2022 from PyO3/pyo3_path
Hygiene: offer a way to set path to pyo3 crate
2 parents bf2cd10 + d0381c2 commit 469d72a

29 files changed

+649
-481
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(crate = "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 `crate` attribute:
153+
154+
```rust
155+
# use pyo3::prelude::*;
156+
# pub extern crate pyo3;
157+
# mod reexported { pub use ::pyo3; }
158+
#[pyclass]
159+
#[pyo3(crate = "reexported::pyo3")]
160+
struct MyClass;
161+
```

pyo3-macros-backend/src/attributes.rs

Lines changed: 14 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 {
@@ -43,6 +43,19 @@ impl Parse for NameAttribute {
4343
}
4444
}
4545

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

pyo3-macros-backend/src/deprecations.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ impl ToTokens for Deprecations {
3333
let ident = deprecation.ident(*span);
3434
quote_spanned!(
3535
*span =>
36-
let _ = ::pyo3::impl_::deprecations::#ident;
36+
let _ = _pyo3::impl_::deprecations::#ident;
3737
)
3838
.to_tokens(tokens)
3939
}

pyo3-macros-backend/src/from_pyobject.rs

Lines changed: 52 additions & 28 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, CrateAttribute, FromPyWithAttribute},
3+
utils::get_pyo3_crate,
4+
};
25
use proc_macro2::TokenStream;
36
use quote::quote;
47
use syn::{
@@ -55,14 +58,14 @@ impl<'a> Enum<'a> {
5558
for (i, var) in self.variants.iter().enumerate() {
5659
let struct_derive = var.build();
5760
let ext = quote!(
58-
let maybe_ret = || -> ::pyo3::PyResult<Self> {
61+
let maybe_ret = || -> _pyo3::PyResult<Self> {
5962
#struct_derive
6063
}();
6164

6265
match maybe_ret {
6366
ok @ ::std::result::Result::Ok(_) => return ok,
6467
::std::result::Result::Err(err) => {
65-
let py = ::pyo3::PyNativeType::py(obj);
68+
let py = _pyo3::PyNativeType::py(obj);
6669
err_reasons.push_str(&::std::format!("{}\n", err.value(py).str()?));
6770
}
6871
}
@@ -82,7 +85,7 @@ impl<'a> Enum<'a> {
8285
#ty_name,
8386
#error_names,
8487
&err_reasons);
85-
::std::result::Result::Err(::pyo3::exceptions::PyTypeError::new_err(err_msg))
88+
::std::result::Result::Err(_pyo3::exceptions::PyTypeError::new_err(err_msg))
8689
)
8790
}
8891
}
@@ -207,8 +210,8 @@ impl<'a> Container<'a> {
207210
);
208211
quote!(
209212
::std::result::Result::Ok(#self_ty{#ident: obj.extract().map_err(|inner| {
210-
let py = ::pyo3::PyNativeType::py(obj);
211-
let new_err = ::pyo3::exceptions::PyTypeError::new_err(#error_msg);
213+
let py = _pyo3::PyNativeType::py(obj);
214+
let new_err = _pyo3::exceptions::PyTypeError::new_err(#error_msg);
212215
new_err.set_cause(py, ::std::option::Option::Some(inner));
213216
new_err
214217
})?})
@@ -222,11 +225,11 @@ impl<'a> Container<'a> {
222225
};
223226
quote!(
224227
::std::result::Result::Ok(#self_ty(obj.extract().map_err(|err| {
225-
let py = ::pyo3::PyNativeType::py(obj);
228+
let py = _pyo3::PyNativeType::py(obj);
226229
let err_msg = ::std::format!("{}: {}",
227230
#error_msg,
228231
err.value(py).str().unwrap());
229-
::pyo3::exceptions::PyTypeError::new_err(err_msg)
232+
_pyo3::exceptions::PyTypeError::new_err(err_msg)
230233
})?))
231234
)
232235
}
@@ -238,9 +241,9 @@ impl<'a> Container<'a> {
238241
for i in 0..len {
239242
let error_msg = format!("failed to extract field {}.{}", quote!(#self_ty), i);
240243
fields.push(quote!(
241-
s.get_item(#i).and_then(::pyo3::types::PyAny::extract).map_err(|inner| {
242-
let py = ::pyo3::PyNativeType::py(obj);
243-
let new_err = ::pyo3::exceptions::PyTypeError::new_err(#error_msg);
244+
s.get_item(#i).and_then(_pyo3::types::PyAny::extract).map_err(|inner| {
245+
let py = _pyo3::PyNativeType::py(obj);
246+
let new_err = _pyo3::exceptions::PyTypeError::new_err(#error_msg);
244247
new_err.set_cause(py, ::std::option::Option::Some(inner));
245248
new_err
246249
})?));
@@ -255,9 +258,9 @@ impl<'a> Container<'a> {
255258
quote!("")
256259
};
257260
quote!(
258-
let s = <::pyo3::types::PyTuple as ::pyo3::conversion::PyTryFrom>::try_from(obj)?;
261+
let s = <_pyo3::types::PyTuple as _pyo3::conversion::PyTryFrom>::try_from(obj)?;
259262
if s.len() != #len {
260-
return ::std::result::Result::Err(::pyo3::exceptions::PyValueError::new_err(#msg))
263+
return ::std::result::Result::Err(_pyo3::exceptions::PyValueError::new_err(#msg))
261264
}
262265
::std::result::Result::Ok(#self_ty(#fields))
263266
)
@@ -279,15 +282,15 @@ impl<'a> Container<'a> {
279282
let extractor = match &attrs.from_py_with {
280283
None => quote!(
281284
#get_field.extract().map_err(|inner| {
282-
let py = ::pyo3::PyNativeType::py(obj);
283-
let new_err = ::pyo3::exceptions::PyTypeError::new_err(#conversion_error_msg);
285+
let py = _pyo3::PyNativeType::py(obj);
286+
let new_err = _pyo3::exceptions::PyTypeError::new_err(#conversion_error_msg);
284287
new_err.set_cause(py, ::std::option::Option::Some(inner));
285288
new_err
286289
})?),
287290
Some(FromPyWithAttribute(expr_path)) => quote! (
288291
#expr_path(#get_field).map_err(|inner| {
289-
let py = ::pyo3::PyNativeType::py(obj);
290-
let new_err = ::pyo3::exceptions::PyTypeError::new_err(#conversion_error_msg);
292+
let py = _pyo3::PyNativeType::py(obj);
293+
let new_err = _pyo3::exceptions::PyTypeError::new_err(#conversion_error_msg);
291294
new_err.set_cause(py, ::std::option::Option::Some(inner));
292295
new_err
293296
})?
@@ -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+
krate: Option<CrateAttribute>,
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+
Crate(CrateAttribute),
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(Token![crate]) {
338+
input.parse().map(ContainerPyO3Attribute::Crate)
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::Crate(path) => {
368+
ensure_spanned!(
369+
options.krate.is_none(),
370+
path.0.span() => "`crate` may only be provided once"
371+
);
372+
options.krate = 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 krate = get_pyo3_crate(&options.krate);
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 #krate 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
}

0 commit comments

Comments
 (0)