Allow `#[classattr]` on associated constants
This commit is contained in:
parent
ab374b40da
commit
f6ac9a0212
|
@ -604,6 +604,20 @@ be mutated at all:
|
|||
pyo3::py_run!(py, my_class, "my_class.my_attribute = 'foo'")
|
||||
```
|
||||
|
||||
If the class attribute is defined with `const` code only, one can also annotate associated
|
||||
constants:
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
# #[pyclass]
|
||||
# struct MyClass {}
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[classattr]
|
||||
const MY_CONST_ATTRIBUTE: &'static str = "foobar";
|
||||
}
|
||||
```
|
||||
|
||||
## Callable objects
|
||||
|
||||
To specify a custom `__call__` method for a custom class, the method needs to be annotated with
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
use crate::pyfunction::parse_name_attribute;
|
||||
use syn::ext::IdentExt;
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct ConstSpec {
|
||||
pub is_class_attr: bool,
|
||||
pub python_name: syn::Ident,
|
||||
}
|
||||
|
||||
impl ConstSpec {
|
||||
// For now, the only valid attribute is `#[classattr]`.
|
||||
pub fn parse(name: &syn::Ident, attrs: &mut Vec<syn::Attribute>) -> syn::Result<ConstSpec> {
|
||||
let mut new_attrs = Vec::new();
|
||||
let mut is_class_attr = false;
|
||||
|
||||
for attr in attrs.iter() {
|
||||
if let syn::Meta::Path(name) = attr.parse_meta()? {
|
||||
if name.is_ident("classattr") {
|
||||
is_class_attr = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
new_attrs.push(attr.clone());
|
||||
}
|
||||
|
||||
attrs.clear();
|
||||
attrs.extend(new_attrs);
|
||||
|
||||
Ok(ConstSpec {
|
||||
is_class_attr,
|
||||
python_name: parse_name_attribute(attrs)?.unwrap_or_else(|| name.unraw()),
|
||||
})
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
mod defs;
|
||||
mod func;
|
||||
mod konst;
|
||||
mod method;
|
||||
mod module;
|
||||
mod pyclass;
|
||||
|
|
|
@ -24,9 +24,18 @@ pub fn impl_methods(ty: &syn::Type, impls: &mut Vec<syn::ImplItem>) -> syn::Resu
|
|||
let mut methods = Vec::new();
|
||||
let mut cfg_attributes = Vec::new();
|
||||
for iimpl in impls.iter_mut() {
|
||||
if let syn::ImplItem::Method(ref mut meth) = iimpl {
|
||||
methods.push(pymethod::gen_py_method(ty, &mut meth.sig, &mut meth.attrs)?);
|
||||
cfg_attributes.push(get_cfg_attributes(&meth.attrs));
|
||||
match iimpl {
|
||||
syn::ImplItem::Method(meth) => {
|
||||
methods.push(pymethod::gen_py_method(ty, &mut meth.sig, &mut meth.attrs)?);
|
||||
cfg_attributes.push(get_cfg_attributes(&meth.attrs));
|
||||
}
|
||||
syn::ImplItem::Const(konst) => {
|
||||
if let Some(meth) = pymethod::gen_py_const(ty, &konst.ident, &mut konst.attrs)? {
|
||||
methods.push(meth);
|
||||
}
|
||||
cfg_attributes.push(get_cfg_attributes(&konst.attrs));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
use crate::konst::ConstSpec;
|
||||
use crate::method::{FnArg, FnSpec, FnType};
|
||||
use crate::utils;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
|
@ -31,7 +32,7 @@ pub fn gen_py_method(
|
|||
FnType::FnClass => impl_py_method_def_class(&spec, &impl_wrap_class(cls, &spec)),
|
||||
FnType::FnStatic => impl_py_method_def_static(&spec, &impl_wrap_static(cls, &spec)),
|
||||
FnType::ClassAttribute => {
|
||||
impl_py_class_attribute(&spec, &impl_wrap_class_attribute(cls, &spec))
|
||||
impl_py_method_class_attribute(&spec, &impl_wrap_class_attribute(cls, &spec))
|
||||
}
|
||||
FnType::Getter => impl_py_getter_def(
|
||||
&spec.python_name,
|
||||
|
@ -62,6 +63,23 @@ fn check_generic(sig: &syn::Signature) -> syn::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn gen_py_const(
|
||||
cls: &syn::Type,
|
||||
name: &syn::Ident,
|
||||
attrs: &mut Vec<syn::Attribute>,
|
||||
) -> syn::Result<Option<TokenStream>> {
|
||||
let spec = ConstSpec::parse(name, attrs)?;
|
||||
if spec.is_class_attr {
|
||||
let wrapper = quote! {
|
||||
fn __wrap(py: pyo3::Python<'_>) -> pyo3::PyObject {
|
||||
pyo3::IntoPy::into_py(#cls::#name, py)
|
||||
}
|
||||
};
|
||||
return Ok(Some(impl_py_const_class_attribute(&spec, &wrapper)));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Generate function wrapper (PyCFunction, PyCFunctionWithKeywords)
|
||||
pub fn impl_wrap(cls: &syn::Type, spec: &FnSpec<'_>, noargs: bool) -> TokenStream {
|
||||
let body = impl_call(cls, &spec);
|
||||
|
@ -249,7 +267,8 @@ pub fn impl_wrap_static(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream {
|
|||
}
|
||||
}
|
||||
|
||||
/// Generate a wrapper for initialization of a class attribute.
|
||||
/// Generate a wrapper for initialization of a class attribute from a method
|
||||
/// annotated with `#[classattr]`.
|
||||
/// To be called in `pyo3::pyclass::initialize_type_object`.
|
||||
pub fn impl_wrap_class_attribute(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream {
|
||||
let name = &spec.name;
|
||||
|
@ -630,7 +649,21 @@ pub fn impl_py_method_def_static(spec: &FnSpec, wrapper: &TokenStream) -> TokenS
|
|||
}
|
||||
}
|
||||
|
||||
pub fn impl_py_class_attribute(spec: &FnSpec<'_>, wrapper: &TokenStream) -> TokenStream {
|
||||
pub fn impl_py_method_class_attribute(spec: &FnSpec<'_>, wrapper: &TokenStream) -> TokenStream {
|
||||
let python_name = &spec.python_name;
|
||||
quote! {
|
||||
pyo3::class::PyMethodDefType::ClassAttribute({
|
||||
#wrapper
|
||||
|
||||
pyo3::class::PyClassAttributeDef {
|
||||
name: stringify!(#python_name),
|
||||
meth: __wrap,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn impl_py_const_class_attribute(spec: &ConstSpec, wrapper: &TokenStream) -> TokenStream {
|
||||
let python_name = &spec.python_name;
|
||||
quote! {
|
||||
pyo3::class::PyMethodDefType::ClassAttribute({
|
||||
|
|
|
@ -16,6 +16,9 @@ struct Bar {
|
|||
|
||||
#[pymethods]
|
||||
impl Foo {
|
||||
#[classattr]
|
||||
const MY_CONST: &'static str = "foobar";
|
||||
|
||||
#[classattr]
|
||||
fn a() -> i32 {
|
||||
5
|
||||
|
@ -53,6 +56,7 @@ fn class_attributes() {
|
|||
let foo_obj = py.get_type::<Foo>();
|
||||
py_assert!(py, foo_obj, "foo_obj.a == 5");
|
||||
py_assert!(py, foo_obj, "foo_obj.B == 'bar'");
|
||||
py_assert!(py, foo_obj, "foo_obj.MY_CONST == 'foobar'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Reference in New Issue