pyo3_macros_backend/
derive_attributes.rs

1use crate::attributes::{
2    self, get_pyo3_options, CrateAttribute, DefaultAttribute, FromPyWithAttribute,
3    IntoPyWithAttribute, RenameAllAttribute,
4};
5use proc_macro2::Span;
6use syn::parse::{Parse, ParseStream};
7use syn::spanned::Spanned;
8use syn::{parenthesized, Attribute, LitStr, Result, Token};
9
10/// Attributes for deriving `FromPyObject`/`IntoPyObject` scoped on containers.
11pub enum ContainerAttribute {
12    /// Treat the Container as a Wrapper, operate directly on its field
13    Transparent(attributes::kw::transparent),
14    /// Force every field to be extracted from item of source Python object.
15    ItemAll(attributes::kw::from_item_all),
16    /// Change the name of an enum variant in the generated error message.
17    ErrorAnnotation(LitStr),
18    /// Change the path for the pyo3 crate
19    Crate(CrateAttribute),
20    /// Converts the field idents according to the [RenamingRule](attributes::RenamingRule) before extraction
21    RenameAll(RenameAllAttribute),
22}
23
24impl Parse for ContainerAttribute {
25    fn parse(input: ParseStream<'_>) -> Result<Self> {
26        let lookahead = input.lookahead1();
27        if lookahead.peek(attributes::kw::transparent) {
28            let kw: attributes::kw::transparent = input.parse()?;
29            Ok(ContainerAttribute::Transparent(kw))
30        } else if lookahead.peek(attributes::kw::from_item_all) {
31            let kw: attributes::kw::from_item_all = input.parse()?;
32            Ok(ContainerAttribute::ItemAll(kw))
33        } else if lookahead.peek(attributes::kw::annotation) {
34            let _: attributes::kw::annotation = input.parse()?;
35            let _: Token![=] = input.parse()?;
36            input.parse().map(ContainerAttribute::ErrorAnnotation)
37        } else if lookahead.peek(Token![crate]) {
38            input.parse().map(ContainerAttribute::Crate)
39        } else if lookahead.peek(attributes::kw::rename_all) {
40            input.parse().map(ContainerAttribute::RenameAll)
41        } else {
42            Err(lookahead.error())
43        }
44    }
45}
46
47#[derive(Default)]
48pub struct ContainerAttributes {
49    /// Treat the Container as a Wrapper, operate directly on its field
50    pub transparent: Option<attributes::kw::transparent>,
51    /// Force every field to be extracted from item of source Python object.
52    pub from_item_all: Option<attributes::kw::from_item_all>,
53    /// Change the name of an enum variant in the generated error message.
54    pub annotation: Option<syn::LitStr>,
55    /// Change the path for the pyo3 crate
56    pub krate: Option<CrateAttribute>,
57    /// Converts the field idents according to the [RenamingRule](attributes::RenamingRule) before extraction
58    pub rename_all: Option<RenameAllAttribute>,
59}
60
61impl ContainerAttributes {
62    pub fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
63        let mut options = ContainerAttributes::default();
64
65        for attr in attrs {
66            if let Some(pyo3_attrs) = get_pyo3_options(attr)? {
67                pyo3_attrs
68                    .into_iter()
69                    .try_for_each(|opt| options.set_option(opt))?;
70            }
71        }
72        Ok(options)
73    }
74
75    fn set_option(&mut self, option: ContainerAttribute) -> syn::Result<()> {
76        macro_rules! set_option {
77            ($key:ident) => {
78                {
79                    ensure_spanned!(
80                        self.$key.is_none(),
81                        $key.span() => concat!("`", stringify!($key), "` may only be specified once")
82                    );
83                    self.$key = Some($key);
84                }
85            };
86        }
87
88        match option {
89            ContainerAttribute::Transparent(transparent) => set_option!(transparent),
90            ContainerAttribute::ItemAll(from_item_all) => set_option!(from_item_all),
91            ContainerAttribute::ErrorAnnotation(annotation) => set_option!(annotation),
92            ContainerAttribute::Crate(krate) => set_option!(krate),
93            ContainerAttribute::RenameAll(rename_all) => set_option!(rename_all),
94        }
95        Ok(())
96    }
97}
98
99#[derive(Clone, Debug)]
100pub enum FieldGetter {
101    GetItem(attributes::kw::item, Option<syn::Lit>),
102    GetAttr(attributes::kw::attribute, Option<syn::LitStr>),
103}
104
105impl FieldGetter {
106    pub fn span(&self) -> Span {
107        match self {
108            FieldGetter::GetItem(item, _) => item.span,
109            FieldGetter::GetAttr(attribute, _) => attribute.span,
110        }
111    }
112}
113
114pub enum FieldAttribute {
115    Getter(FieldGetter),
116    FromPyWith(FromPyWithAttribute),
117    IntoPyWith(IntoPyWithAttribute),
118    Default(DefaultAttribute),
119}
120
121impl Parse for FieldAttribute {
122    fn parse(input: ParseStream<'_>) -> Result<Self> {
123        let lookahead = input.lookahead1();
124        if lookahead.peek(attributes::kw::attribute) {
125            let attr_kw: attributes::kw::attribute = input.parse()?;
126            if input.peek(syn::token::Paren) {
127                let content;
128                let _ = parenthesized!(content in input);
129                let attr_name: LitStr = content.parse()?;
130                if !content.is_empty() {
131                    return Err(content.error(
132                        "expected at most one argument: `attribute` or `attribute(\"name\")`",
133                    ));
134                }
135                ensure_spanned!(
136                    !attr_name.value().is_empty(),
137                    attr_name.span() => "attribute name cannot be empty"
138                );
139                Ok(Self::Getter(FieldGetter::GetAttr(attr_kw, Some(attr_name))))
140            } else {
141                Ok(Self::Getter(FieldGetter::GetAttr(attr_kw, None)))
142            }
143        } else if lookahead.peek(attributes::kw::item) {
144            let item_kw: attributes::kw::item = input.parse()?;
145            if input.peek(syn::token::Paren) {
146                let content;
147                let _ = parenthesized!(content in input);
148                let key = content.parse()?;
149                if !content.is_empty() {
150                    return Err(
151                        content.error("expected at most one argument: `item` or `item(key)`")
152                    );
153                }
154                Ok(Self::Getter(FieldGetter::GetItem(item_kw, Some(key))))
155            } else {
156                Ok(Self::Getter(FieldGetter::GetItem(item_kw, None)))
157            }
158        } else if lookahead.peek(attributes::kw::from_py_with) {
159            input.parse().map(Self::FromPyWith)
160        } else if lookahead.peek(attributes::kw::into_py_with) {
161            input.parse().map(FieldAttribute::IntoPyWith)
162        } else if lookahead.peek(Token![default]) {
163            input.parse().map(Self::Default)
164        } else {
165            Err(lookahead.error())
166        }
167    }
168}
169
170#[derive(Clone, Debug, Default)]
171pub struct FieldAttributes {
172    pub getter: Option<FieldGetter>,
173    pub from_py_with: Option<FromPyWithAttribute>,
174    pub into_py_with: Option<IntoPyWithAttribute>,
175    pub default: Option<DefaultAttribute>,
176}
177
178impl FieldAttributes {
179    /// Extract the field attributes.
180    pub fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
181        let mut options = FieldAttributes::default();
182
183        for attr in attrs {
184            if let Some(pyo3_attrs) = get_pyo3_options(attr)? {
185                pyo3_attrs
186                    .into_iter()
187                    .try_for_each(|opt| options.set_option(opt))?;
188            }
189        }
190        Ok(options)
191    }
192
193    fn set_option(&mut self, option: FieldAttribute) -> syn::Result<()> {
194        macro_rules! set_option {
195            ($key:ident) => {
196                set_option!($key, concat!("`", stringify!($key), "` may only be specified once"))
197            };
198            ($key:ident, $msg: expr) => {{
199                ensure_spanned!(
200                    self.$key.is_none(),
201                    $key.span() => $msg
202                );
203                self.$key = Some($key);
204            }}
205        }
206
207        match option {
208            FieldAttribute::Getter(getter) => {
209                set_option!(getter, "only one of `attribute` or `item` can be provided")
210            }
211            FieldAttribute::FromPyWith(from_py_with) => set_option!(from_py_with),
212            FieldAttribute::IntoPyWith(into_py_with) => set_option!(into_py_with),
213            FieldAttribute::Default(default) => set_option!(default),
214        }
215        Ok(())
216    }
217}
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here