Skip to content

Commit 2a00a7b

Browse files
authored
Merge pull request #160 from kevinheavey/pyo3-26
upgrade to pyo3 0.26
2 parents 82781ea + d5a1da6 commit 2a00a7b

File tree

10 files changed

+301
-27
lines changed

10 files changed

+301
-27
lines changed

Cargo.lock

Lines changed: 16 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ crate-type = ["cdylib", "rlib"]
1616
[dependencies]
1717
bincode = { workspace = true }
1818
derive_more = { workspace = true }
19-
pyo3 = { workspace = true, features = ["macros", "extension-module", "abi3-py37"] }
19+
pyo3 = { workspace = true, features = ["macros", "extension-module", "abi3-py38"] }
2020
serde = { workspace = true }
2121
solana-address-lookup-table-interface = { workspace = true }
2222
solana-sdk-ids = { workspace = true }
@@ -71,8 +71,8 @@ derive_more = "0.99.17"
7171
five8 = "0.2.1"
7272
litesvm = "0.8"
7373
log = "0.4"
74-
pyo3 = { version = "0.23", default-features = false }
75-
pythonize = "0.23"
74+
pyo3 = { version = "0.26", default-features = false }
75+
pythonize = "0.26"
7676
serde = "^1.0.188"
7777
serde_bytes = "0.11.12"
7878
serde_cbor = "^0.11.2"
@@ -174,3 +174,6 @@ spl-token-interface = "2"
174174
thiserror = "^1.0.31"
175175
time = "0.3.35"
176176
zeroize = "1.7"
177+
178+
[patch.crates-io]
179+
dict_derive = { path = "crates/dict-derive" }

crates/account-decoder/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ impl ParsedAccount {
9999
}
100100

101101
#[getter]
102-
pub fn parsed(&self, py: Python<'_>) -> PyResult<PyObject> {
103-
handle_py_value_err(pythonize(py, &self.0.parsed))
102+
pub fn parsed(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
103+
handle_py_value_err(pythonize(py, &self.0.parsed).map(Bound::unbind))
104104
}
105105

106106
#[getter]

crates/dict-derive/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "dict_derive"
3+
version = "0.6.0"
4+
edition = "2018"
5+
authors = ["Guilherme Perinazzo <[email protected]>"]
6+
description = "Derive macros for some PyO3 traits to convert python dicts into rust structs"
7+
license = "Apache-2.0"
8+
repository = "https://github.com/gperinazzo/dict-derive"
9+
10+
[lib]
11+
proc-macro = true
12+
13+
[dependencies]
14+
proc-macro2 = "1"
15+
quote = "1"
16+
syn = { version = "1", features = ["full"] }

crates/dict-derive/src/from.rs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
use proc_macro2::TokenStream;
2+
use quote::{quote, quote_spanned, ToTokens};
3+
use syn::{parse_quote, spanned::Spanned, Data, DeriveInput, Field, Ident};
4+
5+
fn is_option(ty: &syn::Type) -> bool {
6+
let path = match *ty {
7+
syn::Type::Path(ref p) if p.qself.is_none() => &p.path,
8+
_ => return false,
9+
};
10+
11+
path.segments
12+
.iter()
13+
.map(|segment| segment.ident.to_string())
14+
.any(|x| x.as_str() == "Option")
15+
}
16+
17+
fn map_extraction(field: Field) -> TokenStream {
18+
let ident = match &field.ident {
19+
Some(i) => i,
20+
None => {
21+
return syn::Error::new(field.span(), "Unnamed fields are not supported")
22+
.to_compile_error()
23+
}
24+
};
25+
26+
let name = ident.to_string();
27+
28+
let function = if is_option(&field.ty) {
29+
Ident::new("extract_optional", field.ty.span())
30+
} else {
31+
Ident::new("extract_required", field.ty.span())
32+
};
33+
34+
quote_spanned! {ident.span()=>
35+
#ident: #function(dict, #name)?
36+
}
37+
}
38+
39+
fn extraction_functions() -> TokenStream {
40+
quote! {
41+
fn map_exception(name: &str, e: PyErr) -> PyErr {
42+
PyErr::new::<PyTypeError, _>(format!("Unable to convert key: {}. Error: {}", name, e))
43+
}
44+
45+
fn extract_required<'a, T>(dict: &Bound<'a, PyDict>, name: &str) -> PyResult<T>
46+
where
47+
T: FromPyObject<'a>,
48+
{
49+
match PyDictMethods::get_item(dict, name)? {
50+
Some(v) => FromPyObject::extract_bound(&v)
51+
.map_err(|err| map_exception(name, err)),
52+
None => Err(PyErr::new::<PyValueError, _>(format!(
53+
"Missing required key: {}",
54+
name
55+
))),
56+
}
57+
}
58+
59+
fn extract_optional<'a, T>(dict: &Bound<'a, PyDict>, name: &str) -> PyResult<std::option::Option<T>>
60+
where
61+
T: FromPyObject<'a>,
62+
{
63+
match PyDictMethods::get_item(dict, name)? {
64+
Some(v) => FromPyObject::extract_bound(&v)
65+
.map_err(|err| map_exception(name, err)),
66+
None => Ok(None),
67+
}
68+
}
69+
}
70+
}
71+
72+
pub fn from_impl(ast: DeriveInput) -> TokenStream {
73+
let struct_data = match ast.data {
74+
Data::Struct(s) => s,
75+
Data::Enum(e) => {
76+
return syn::Error::new(e.enum_token.span, "Deriving enums is not supported")
77+
.to_compile_error();
78+
}
79+
Data::Union(u) => {
80+
return syn::Error::new(u.union_token.span, "Deriving unions is not supported")
81+
.to_compile_error();
82+
}
83+
};
84+
85+
let extractions = struct_data.fields.into_iter().map(map_extraction);
86+
87+
let name = ast.ident;
88+
let mut impl_generics = ast.generics.clone();
89+
90+
let lifetimes_count = impl_generics.lifetimes().count();
91+
92+
if lifetimes_count > 1 {
93+
return syn::Error::new(
94+
impl_generics.span(),
95+
"Deriving structs with more than one lifetime is not supported",
96+
)
97+
.to_compile_error();
98+
} else if lifetimes_count == 0 {
99+
impl_generics.params.push(parse_quote!('source));
100+
};
101+
102+
let lifetime = impl_generics
103+
.lifetimes()
104+
.next()
105+
.map(|lt| lt.lifetime.to_token_stream())
106+
.unwrap();
107+
108+
let (_, ty_generics, where_clause) = ast.generics.split_for_impl();
109+
let (impl_generics, _, _) = impl_generics.split_for_impl();
110+
111+
let functions = extraction_functions();
112+
113+
quote! {
114+
impl #impl_generics ::pyo3::FromPyObject<#lifetime> for #name #ty_generics #where_clause {
115+
fn extract_bound(
116+
obj: &::pyo3::prelude::Bound<#lifetime, ::pyo3::types::PyAny>,
117+
) -> ::pyo3::PyResult<Self> {
118+
use ::pyo3::{FromPyObject, PyErr, PyResult};
119+
use ::pyo3::exceptions::{PyTypeError, PyValueError};
120+
use ::pyo3::prelude::Bound;
121+
use ::pyo3::types::{PyAnyMethods, PyDict, PyDictMethods};
122+
let dict = obj.downcast::<PyDict>().map_err(|_| {
123+
PyErr::new::<PyTypeError, _>("Invalid type to convert, expected dict")
124+
})?;
125+
126+
#functions
127+
128+
::std::result::Result::Ok(#name {
129+
#(#extractions),*
130+
})
131+
}
132+
}
133+
}
134+
}

crates/dict-derive/src/into.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use proc_macro2::TokenStream;
2+
use quote::{quote, quote_spanned};
3+
use syn::{parse_quote, spanned::Spanned, Data, DeriveInput, Field};
4+
5+
fn map_fields(field: Field) -> TokenStream {
6+
let ident = match &field.ident {
7+
Some(i) => i,
8+
None => {
9+
return syn::Error::new(field.span(), "Unnamed fields are not supported")
10+
.to_compile_error()
11+
}
12+
};
13+
14+
let name = ident.to_string();
15+
16+
quote_spanned! {field.ty.span()=>
17+
dict.set_item(#name, self.#ident)?;
18+
}
19+
}
20+
21+
pub fn into_impl(ast: DeriveInput) -> TokenStream {
22+
let struct_data = match ast.data {
23+
Data::Struct(s) => s,
24+
Data::Enum(e) => {
25+
return syn::Error::new(e.enum_token.span, "Deriving enums is not supported")
26+
.to_compile_error();
27+
}
28+
Data::Union(u) => {
29+
return syn::Error::new(u.union_token.span, "Deriving unions is not supported")
30+
.to_compile_error();
31+
}
32+
};
33+
34+
let field_setters = struct_data.fields.into_iter().map(map_fields);
35+
36+
let name = ast.ident;
37+
let (_, ty_generics, where_clause) = ast.generics.split_for_impl();
38+
39+
let mut impl_generics = ast.generics.clone();
40+
impl_generics.params.insert(0, parse_quote!('py));
41+
let (impl_generics, _, _) = impl_generics.split_for_impl();
42+
43+
quote! {
44+
impl #impl_generics ::pyo3::IntoPyObject<'py> for #name #ty_generics #where_clause {
45+
type Target = ::pyo3::types::PyDict;
46+
type Output = ::pyo3::Bound<'py, ::pyo3::types::PyDict>;
47+
type Error = ::pyo3::PyErr;
48+
49+
fn into_pyobject(self, py: ::pyo3::Python<'py>) -> ::pyo3::PyResult<Self::Output> {
50+
use ::pyo3::types::PyDict;
51+
let dict = PyDict::new(py);
52+
#(#field_setters)*
53+
::std::result::Result::Ok(dict)
54+
}
55+
}
56+
}
57+
}

crates/dict-derive/src/lib.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
mod from;
2+
mod into;
3+
4+
use from::from_impl;
5+
use into::into_impl;
6+
use proc_macro::TokenStream;
7+
use syn::DeriveInput;
8+
9+
#[proc_macro_derive(FromPyObject)]
10+
pub fn derive_from_py_object(input: TokenStream) -> TokenStream {
11+
let ast = syn::parse_macro_input!(input as DeriveInput);
12+
TokenStream::from(from_impl(ast))
13+
}
14+
15+
#[proc_macro_derive(IntoPyObject)]
16+
pub fn derive_into_py_object(input: TokenStream) -> TokenStream {
17+
let ast = syn::parse_macro_input!(input as DeriveInput);
18+
TokenStream::from(into_impl(ast))
19+
}

0 commit comments

Comments
 (0)