Skip to content

Commit

Permalink
General purpose python macros (kaspanet#58)
Browse files Browse the repository at this point in the history
* kaspa-python-macros

* cleanup + rename 'inner' to 'client'
  • Loading branch information
aspect authored and smartgoo committed Sep 17, 2024
1 parent a1ecdee commit 816fc35
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 73 deletions.
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

118 changes: 61 additions & 57 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ members = [
"metrics/perf_monitor",
"utils/alloc",
"python",
"python/macros",
]

[workspace.package]
Expand All @@ -82,63 +83,66 @@ include = [
]

[workspace.dependencies]
# kaspa-testing-integration = { version = "0.15.1", path = "testing/integration" }
kaspa-addresses = { version = "0.15.1", path = "crypto/addresses" }
kaspa-addressmanager = { version = "0.15.1", path = "components/addressmanager" }
kaspa-bip32 = { version = "0.15.1", path = "wallet/bip32" }
kaspa-cli = { version = "0.15.1", path = "cli" }
kaspa-connectionmanager = { version = "0.15.1", path = "components/connectionmanager" }
kaspa-consensus = { version = "0.15.1", path = "consensus" }
kaspa-consensus-core = { version = "0.15.1", path = "consensus/core" }
kaspa-consensus-client = { version = "0.15.1", path = "consensus/client" }
kaspa-consensus-notify = { version = "0.15.1", path = "consensus/notify" }
kaspa-consensus-wasm = { version = "0.15.1", path = "consensus/wasm" }
kaspa-consensusmanager = { version = "0.15.1", path = "components/consensusmanager" }
kaspa-core = { version = "0.15.1", path = "core" }
kaspa-daemon = { version = "0.15.1", path = "daemon" }
kaspa-database = { version = "0.15.1", path = "database" }
kaspa-grpc-client = { version = "0.15.1", path = "rpc/grpc/client" }
kaspa-grpc-core = { version = "0.15.1", path = "rpc/grpc/core" }
kaspa-grpc-server = { version = "0.15.1", path = "rpc/grpc/server" }
kaspa-hashes = { version = "0.15.1", path = "crypto/hashes" }
kaspa-index-core = { version = "0.15.1", path = "indexes/core" }
kaspa-index-processor = { version = "0.15.1", path = "indexes/processor" }
kaspa-math = { version = "0.15.1", path = "math" }
kaspa-merkle = { version = "0.15.1", path = "crypto/merkle" }
kaspa-metrics-core = { version = "0.15.1", path = "metrics/core" }
kaspa-mining = { version = "0.15.1", path = "mining" }
kaspa-mining-errors = { version = "0.15.1", path = "mining/errors" }
kaspa-muhash = { version = "0.15.1", path = "crypto/muhash" }
kaspa-notify = { version = "0.15.1", path = "notify" }
kaspa-p2p-flows = { version = "0.15.1", path = "protocol/flows" }
kaspa-p2p-lib = { version = "0.15.1", path = "protocol/p2p" }
kaspa-perf-monitor = { version = "0.15.1", path = "metrics/perf_monitor" }
kaspa-pow = { version = "0.15.1", path = "consensus/pow" }
kaspa-python = { version = "0.15.1", path = "python" }
kaspa-rpc-core = { version = "0.15.1", path = "rpc/core" }
kaspa-rpc-macros = { version = "0.15.1", path = "rpc/macros" }
kaspa-rpc-service = { version = "0.15.1", path = "rpc/service" }
kaspa-txscript = { version = "0.15.1", path = "crypto/txscript" }
kaspa-txscript-errors = { version = "0.15.1", path = "crypto/txscript/errors" }
kaspa-utils = { version = "0.15.1", path = "utils" }
kaspa-utils-tower = { version = "0.15.1", path = "utils/tower" }
kaspa-utxoindex = { version = "0.15.1", path = "indexes/utxoindex" }
kaspa-wallet = { version = "0.15.1", path = "wallet/native" }
kaspa-wallet-cli-wasm = { version = "0.15.1", path = "wallet/wasm" }
kaspa-wallet-keys = { version = "0.15.1", path = "wallet/keys" }
kaspa-wallet-pskt = { version = "0.15.1", path = "wallet/pskt" }
kaspa-wallet-core = { version = "0.15.1", path = "wallet/core" }
kaspa-wallet-macros = { version = "0.15.1", path = "wallet/macros" }
kaspa-wasm = { version = "0.15.1", path = "wasm" }
kaspa-wasm-core = { version = "0.15.1", path = "wasm/core" }
kaspa-wrpc-client = { version = "0.15.1", path = "rpc/wrpc/client" }
kaspa-wrpc-proxy = { version = "0.15.1", path = "rpc/wrpc/proxy" }
kaspa-wrpc-python = { version = "0.15.1", path = "rpc/wrpc/python" }
kaspa-wrpc-server = { version = "0.15.1", path = "rpc/wrpc/server" }
kaspa-wrpc-wasm = { version = "0.15.1", path = "rpc/wrpc/wasm" }
kaspa-wrpc-example-subscriber = { version = "0.15.1", path = "rpc/wrpc/examples/subscriber" }
kaspad = { version = "0.15.1", path = "kaspad" }
kaspa-alloc = { version = "0.15.1", path = "utils/alloc" }
# kaspa-testing-integration = { version = "0.14.1", path = "testing/integration" }
kaspa-addresses = { version = "0.14.1", path = "crypto/addresses" }
kaspa-addressmanager = { version = "0.14.1", path = "components/addressmanager" }
kaspa-bip32 = { version = "0.14.1", path = "wallet/bip32" }
kaspa-resolver = { version = "0.14.1", path = "rpr/wrpc/resolver" }
kaspa-cli = { version = "0.14.1", path = "cli" }
kaspa-connectionmanager = { version = "0.14.1", path = "components/connectionmanager" }
kaspa-consensus = { version = "0.14.1", path = "consensus" }
kaspa-consensus-core = { version = "0.14.1", path = "consensus/core" }
kaspa-consensus-client = { version = "0.14.1", path = "consensus/client" }
kaspa-consensus-notify = { version = "0.14.1", path = "consensus/notify" }
kaspa-consensus-wasm = { version = "0.14.1", path = "consensus/wasm" }
kaspa-consensusmanager = { version = "0.14.1", path = "components/consensusmanager" }
kaspa-core = { version = "0.14.1", path = "core" }
kaspa-daemon = { version = "0.14.1", path = "daemon" }
kaspa-database = { version = "0.14.1", path = "database" }
kaspa-grpc-client = { version = "0.14.1", path = "rpc/grpc/client" }
kaspa-grpc-core = { version = "0.14.1", path = "rpc/grpc/core" }
kaspa-grpc-server = { version = "0.14.1", path = "rpc/grpc/server" }
kaspa-hashes = { version = "0.14.1", path = "crypto/hashes" }
kaspa-index-core = { version = "0.14.1", path = "indexes/core" }
kaspa-index-processor = { version = "0.14.1", path = "indexes/processor" }
kaspa-math = { version = "0.14.1", path = "math" }
kaspa-merkle = { version = "0.14.1", path = "crypto/merkle" }
kaspa-metrics-core = { version = "0.14.1", path = "metrics/core" }
kaspa-mining = { version = "0.14.1", path = "mining" }
kaspa-mining-errors = { version = "0.14.1", path = "mining/errors" }
kaspa-muhash = { version = "0.14.1", path = "crypto/muhash" }
kaspa-notify = { version = "0.14.1", path = "notify" }
kaspa-p2p-flows = { version = "0.14.1", path = "protocol/flows" }
kaspa-p2p-lib = { version = "0.14.1", path = "protocol/p2p" }
kaspa-perf-monitor = { version = "0.14.1", path = "metrics/perf_monitor" }
kaspa-pow = { version = "0.14.1", path = "consensus/pow" }
kaspa-python = { version = "0.14.1", path = "python" }
kaspa-python-macros = { version = "0.15.1", path = "python/macros" }
kaspa-rpc-core = { version = "0.14.1", path = "rpc/core" }
kaspa-rpc-macros = { version = "0.14.1", path = "rpc/macros" }
kaspa-rpc-service = { version = "0.14.1", path = "rpc/service" }
kaspa-txscript = { version = "0.14.1", path = "crypto/txscript" }
kaspa-txscript-errors = { version = "0.14.1", path = "crypto/txscript/errors" }
kaspa-utils = { version = "0.14.1", path = "utils" }
kaspa-utils-tower = { version = "0.14.1", path = "utils/tower" }
kaspa-utxoindex = { version = "0.14.1", path = "indexes/utxoindex" }
kaspa-wallet = { version = "0.14.1", path = "wallet/native" }
kaspa-wallet-cli-wasm = { version = "0.14.1", path = "wallet/wasm" }
kaspa-wallet-keys = { version = "0.14.1", path = "wallet/keys" }
kaspa-wallet-pskt = { version = "0.14.1", path = "wallet/pskt" }
kaspa-wallet-core = { version = "0.14.1", path = "wallet/core" }
kaspa-wallet-macros = { version = "0.14.1", path = "wallet/macros" }
kaspa-wasm = { version = "0.14.1", path = "wasm" }
kaspa-wasm-core = { version = "0.14.1", path = "wasm/core" }
kaspa-wrpc-client = { version = "0.14.1", path = "rpc/wrpc/client" }
kaspa-wrpc-core = { version = "0.14.1", path = "rpc/wrpc/core" }
kaspa-wrpc-proxy = { version = "0.14.1", path = "rpc/wrpc/proxy" }
kaspa-wrpc-python = { version = "0.14.1", path = "rpc/wrpc/python" }
kaspa-wrpc-server = { version = "0.14.1", path = "rpc/wrpc/server" }
kaspa-wrpc-wasm = { version = "0.14.1", path = "rpc/wrpc/wasm" }
kaspa-wrpc-example-subscriber = { version = "0.14.1", path = "rpc/wrpc/examples/subscriber" }
kaspad = { version = "0.14.1", path = "kaspad" }
kaspa-alloc = { version = "0.14.1", path = "utils/alloc" }

# external
aes = "0.8.3"
Expand Down
25 changes: 25 additions & 0 deletions python/macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "kaspa-python-macros"
rust-version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
repository.workspace = true
keywords = ["kaspa","python"]
categories = []
exclude = ["/.*", "/test"]
description = """
Macros for the Kaspa Python bindings
"""

[lib]
proc-macro = true

[dependencies]
convert_case.workspace = true
proc-macro-error = { version = "1.0.0", default-features = false }
proc-macro2 = { version = "1.0.43" }
quote = "1.0.21"
regex.workspace = true
syn = {version="1.0.99",features=["full","fold","extra-traits","parsing","proc-macro"]} # do not update!
10 changes: 10 additions & 0 deletions python/macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use proc_macro::TokenStream;
use proc_macro_error::proc_macro_error;

mod py_async;

#[proc_macro]
#[proc_macro_error]
pub fn py_async(input: TokenStream) -> TokenStream {
py_async::py_async(input)
}
58 changes: 58 additions & 0 deletions python/macros/src/py_async.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use std::convert::Into;
use syn::{
parse::{Parse, ParseStream},
parse_macro_input,
punctuated::Punctuated,
Error, Expr, ExprAsync, Result, Token,
};

#[derive(Debug)]
struct PyAsync {
py: Expr,
block: ExprAsync,
}

impl Parse for PyAsync {
fn parse(input: ParseStream) -> Result<Self> {
let parsed = Punctuated::<Expr, Token![,]>::parse_terminated(input).unwrap();
if parsed.len() != 2 {
return Err(Error::new_spanned(parsed, "usage: py_async!{py, async move { Ok(()) }}".to_string()));
}

let mut iter = parsed.iter();
// python object (py: Python)
let py = iter.next().unwrap().clone();

// async block to encapsulate
let block = match iter.next().unwrap().clone() {
Expr::Async(block) => block,
statement => {
return Err(Error::new_spanned(statement, "the argument must be an async block".to_string()));
}
};

Ok(PyAsync { py, block })
}
}

impl ToTokens for PyAsync {
fn to_tokens(&self, tokens: &mut TokenStream) {
let PyAsync { py, block } = self;

quote! {
let __fut__ = #block;
let __py_fut__ = pyo3_asyncio_0_21::tokio::future_into_py(#py, __fut__)?;
pyo3::prelude::Python::with_gil(|py| Ok(__py_fut__.into_py(#py)))
}
.to_tokens(tokens);
}
}

pub fn py_async(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let py_async = parse_macro_input!(input as PyAsync);
let token_stream = py_async.to_token_stream();
// println!("MACRO: {}", token_stream.to_string());
token_stream.into()
}
2 changes: 1 addition & 1 deletion rpc/macros/src/wrpc/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ impl ToTokens for RpcTable {
impl RpcClient {
fn #fn_call(&self, py: Python) -> PyResult<Py<PyAny>> {
// Returns result as JSON string
let client = self.inner.clone();
let client = self.client.clone();

// TODO - receive argument from Python and deserialize it
// explore https://docs.rs/serde-pyobject/latest/serde_pyobject/ for arg intake / return
Expand Down
1 change: 1 addition & 0 deletions rpc/wrpc/python/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ kaspa-consensus-core.workspace = true
kaspa-rpc-core.workspace = true
kaspa-rpc-macros.workspace = true
kaspa-wrpc-client.workspace = true
kaspa-python-macros.workspace = true
pyo3.workspace = true
pyo3-asyncio-0-21.workspace = true
serde_json.workspace = true
Expand Down
40 changes: 25 additions & 15 deletions rpc/wrpc/python/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
use kaspa_python_macros::py_async;
use kaspa_rpc_core::api::rpc::RpcApi;
use kaspa_rpc_core::model::*;
use kaspa_rpc_macros::build_wrpc_python_interface;
use kaspa_wrpc_client::{
client::{ConnectOptions, ConnectStrategy},
KaspaRpcClient, WrpcEncoding,
};
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use std::time::Duration;

#[pyclass]
pub struct RpcClient {
inner: KaspaRpcClient,
client: KaspaRpcClient,
// url: String,
// encoding: Option<WrpcEncoding>,
// verbose : Option<bool>,
Expand All @@ -34,23 +36,23 @@ impl RpcClient {
client.connect(Some(options)).await.map_err(|e| pyo3::exceptions::PyException::new_err(e.to_string()))?;

Python::with_gil(|py| {
Py::new(py, RpcClient { inner: client })
Py::new(py, RpcClient { client })
.map(|py_rpc_client| py_rpc_client.into_py(py))
.map_err(|e| pyo3::exceptions::PyException::new_err(e.to_string()))
})
})
}

fn is_connected(&self) -> bool {
self.inner.is_connected()
self.client.is_connected()
}

fn get_server_info(&self, py: Python) -> PyResult<Py<PyAny>> {
// Returns result as JSON string
let inner = self.inner.clone();
let client = self.client.clone();

let fut = async move {
let r = inner.get_server_info().await?;
let r = client.get_server_info().await?;
serde_json::to_string(&r).map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))
};

Expand All @@ -59,25 +61,33 @@ impl RpcClient {
Python::with_gil(|py| Ok(py_fut.into_py(py)))
}

fn get_block_dag_info(&self, py: Python) -> PyResult<Py<PyAny>> {
// Returns result as JSON string
let inner = self.inner.clone();
// fn get_block_dag_info(&self, py: Python) -> PyResult<Py<PyAny>> {
// // Returns result as JSON string
// let client = self.client.clone();

let fut = async move {
let r = inner.get_block_dag_info().await?;
serde_json::to_string(&r).map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))
};
// let fut = async move {
// let r = client.get_block_dag_info().await?;
// serde_json::to_string(&r).map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))
// };

let py_fut = pyo3_asyncio_0_21::tokio::future_into_py(py, fut)?;
// let py_fut = pyo3_asyncio_0_21::tokio::future_into_py(py, fut)?;

Python::with_gil(|py| Ok(py_fut.into_py(py)))
// Python::with_gil(|py| Ok(py_fut.into_py(py)))
// }

fn get_block_dag_info(&self, py: Python) -> PyResult<Py<PyAny>> {
let client = self.client.clone();
py_async! {py, async move {
let response = client.get_block_dag_info_call(GetBlockDagInfoRequest { }).await?;
serde_json::to_string(&response).map_err(|err| PyValueError::new_err(err.to_string()))
}}
}
}

#[pymethods]
impl RpcClient {
fn is_connected_test(&self) -> bool {
self.inner.is_connected()
self.client.is_connected()
}
}

Expand Down

0 comments on commit 816fc35

Please sign in to comment.