Skip to content

Commit

Permalink
Inspect attributes marked with #[pyo3(get, set)]
Browse files Browse the repository at this point in the history
  • Loading branch information
CLOVIS-AI committed Jun 15, 2022
1 parent f793860 commit 53f2e94
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 13 deletions.
66 changes: 58 additions & 8 deletions pyo3-macros-backend/src/inspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,53 @@ pub(crate) fn generate_class_inspection(

let name = Literal::string(&*get_class_python_name(cls, args).to_string());

let mut fields: Vec<TokenStream> = vec![];
for (field, options) in field_options {
let typ = generate_type(&field.ty)
.map(|it| Box::new(it) as Box<dyn ToTokens>)
.unwrap_or_else(|| Box::new(cls));
let name = options.name.as_ref()
.map(|it| Literal::string(&*it.value.0.to_string()))
.or_else(|| field.ident.as_ref().map(|it| Literal::string(&*it.to_string())));

if let Some(name) = name {
if options.get {
fields.push(quote! {
&_pyo3::inspect::fields::FieldInfo {
name: #name,
kind: _pyo3::inspect::fields::FieldKind::Getter,
py_type: ::std::option::Option::Some(|| <#typ as _pyo3::conversion::IntoPy<_>>::type_output()),
arguments: &[],
}
});
}

if options.set {
fields.push(quote! {
&_pyo3::inspect::fields::FieldInfo {
name: #name,
kind: _pyo3::inspect::fields::FieldKind::Setter,
py_type: ::std::option::Option::Some(|| _pyo3::inspect::types::TypeInfo::None),
arguments: &[
_pyo3::inspect::fields::ArgumentInfo {
name: #name,
kind: _pyo3::inspect::fields::ArgumentKind::Position,
py_type: ::std::option::Option::Some(|| <#typ as _pyo3::conversion::IntoPy<_>>::type_output()),
default_value: false,
is_modified: false,
}
],
}
});
}
}
}

let field_size = Literal::usize_suffixed(fields.len());

quote! {
const #class_field_info: [&'static _pyo3::inspect::fields::FieldInfo<'static>; 0] = [
//TODO
const #class_field_info: [&'static _pyo3::inspect::fields::FieldInfo<'static>; #field_size] = [
#(#fields),*
];

const #class_info: _pyo3::inspect::classes::ClassStructInfo<'static> = _pyo3::inspect::classes::ClassStructInfo {
Expand Down Expand Up @@ -102,12 +146,9 @@ pub(crate) fn generate_fields_inspection(
FnType::FnModule => todo!("FnModule is not currently supported"),
FnType::ClassAttribute => quote!(_pyo3::inspect::fields::FieldKind::ClassAttribute),
};
let field_type = match &field.spec.output {
Type::Path(path) if path.path.get_ident().filter(|i| i.to_string() == "Self").is_some() => {
cls.to_token_stream()
}
other => other.to_token_stream(),
};
let field_type = generate_type(&field.spec.output)
.map(|it| it.to_token_stream())
.unwrap_or_else(|| cls.to_token_stream());

let output = quote! {
fn #field_type_fn_name() -> _pyo3::inspect::types::TypeInfo {
Expand All @@ -127,6 +168,15 @@ pub(crate) fn generate_fields_inspection(
(output, field_info_name)
}

fn generate_type(target: &Type) -> Option<impl ToTokens + '_> {
match target {
Type::Path(path) if path.path.get_ident().filter(|i| i.to_string() == "Self").is_some() => {
None
}
other => Some(other)
}
}

/// Generates a unique identifier based on a type and (optionally) a field.
///
/// For the same input values, the result should be the same output, and for different input values,
Expand Down
6 changes: 3 additions & 3 deletions pyo3-macros-backend/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,9 @@ pub fn build_py_class(

/// `#[pyo3()]` options for pyclass fields
pub(crate) struct FieldPyO3Options {
get: bool,
set: bool,
name: Option<NameAttribute>,
pub(crate) get: bool,
pub(crate) set: bool,
pub(crate) name: Option<NameAttribute>,
}

enum FieldPyO3Option {
Expand Down
45 changes: 43 additions & 2 deletions tests/test_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use pyo3::inspect::classes::{ClassInfo, ClassStructInfo, InspectClass};
use pyo3::inspect::fields::{ArgumentInfo, ArgumentKind, FieldInfo, FieldKind};
use pyo3::inspect::interface::InterfaceGenerator;
use pyo3::inspect::types::TypeInfo;
use pyo3::types::PyType;

mod common;

Expand Down Expand Up @@ -122,7 +123,7 @@ const EXPECTED_COMPLICATED: &str = r#"class Complicated(Simple):
def value(self, value: int) -> None: ...
def __new__(cls, /, foo: str = ..., **parent: Any) -> None: ...
@staticmethod
def static(input: Complicated) -> Complicated: ...
def staticmeth(input: Complicated) -> Complicated: ...
@classmethod
def classmeth(cls, /, input: Union[Complicated, str, int]) -> Complicated: ...
counter: int = ...
Expand Down Expand Up @@ -180,7 +181,7 @@ fn complicated_manual() {
],
},
&FieldInfo {
name: "static",
name: "staticmeth",
kind: FieldKind::StaticMethod,
py_type: Some(|| TypeInfo::Class { module: None, name: "Complicated" }),
arguments: &[
Expand Down Expand Up @@ -219,4 +220,44 @@ fn complicated_manual() {
assert_eq!(EXPECTED_COMPLICATED, format!("{}", InterfaceGenerator::new(class)))
}

#[pyclass]
struct Complicated {
#[pyo3(get, set)]
value: usize,
}

#[allow(unused_variables)]
#[pymethods]
impl Complicated {
fn new(foo: PyObject, parent: PyObject) -> Self {
unreachable!("This is just a stub")
}

#[pyo3(name = "staticmeth")]
#[staticmethod]
fn static_method(input: PyObject) -> Complicated {
unreachable!("This is just a stub")
}

#[pyo3(name = "classmeth")]
#[classmethod]
fn class_method(cls: &PyType, input: ClassMethodInput) -> Complicated {
unreachable!("This is just a stub")
}
}

#[derive(FromPyObject)]
enum ClassMethodInput {
// Complicated(PyCell<Complicated>),
String(String),
Int(usize),
}

#[test]
fn complicated_derived() {
let inspect = Complicated::inspect();
println!("Inspection results: {:#?}", inspect);
assert_eq!(EXPECTED_COMPLICATED, format!("{}", InterfaceGenerator::new(inspect)))
}

//endregion

0 comments on commit 53f2e94

Please sign in to comment.