Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Implement Python wrapper for parity-clib and example #10443

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 157 additions & 33 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions parity-clib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ futures = "0.1.6"
jni = { version = "0.11", optional = true }
panic_hook = { path = "../util/panic-hook" }
parity-ethereum = { path = "../", default-features = false }
pyo3 = { version = "0.6.0-alpha.4", optional = true, features = ["extension-module"] }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pyo3 will require the nightly toolchain and currently we only use stable features

tokio = "0.1.11"
tokio-current-thread = "0.1.3"

Expand Down
1 change: 1 addition & 0 deletions parity-clib/examples/python/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
venv/
9 changes: 9 additions & 0 deletions parity-clib/examples/python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
parity-clib: Python example
===================================

An example Python application to demonstrate how to use `pyo3` bindings to parity-ethereum.

## How to compile and run

1. Make sure you have python3 and python3-venv installed.
2. Run `run.sh`
39 changes: 39 additions & 0 deletions parity-clib/examples/python/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright 2019 Parity Technologies (UK) Ltd.
# This file is part of Parity.
#
# Parity is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Parity is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Parity. If not, see <http://www.gnu.org/licenses/>.

from itertools import islice

from parity import Parity

# Set up Parity
opts = ["--no-ipc", "--jsonrpc-apis=all", "--chain", "kovan"]
p = Parity(opts)

# Run a RPC query and print the results
query = "{\"method\":\"parity_versionInfo\",\"params\":[],\"id\":1,\"jsonrpc\":\"2.0\"}"
print('version info:', p.rpc_query_sync(query))

# Subscribe to a websocket event
ws_query = "{\"method\":\"parity_subscribe\",\"params\":[\"parity_netPeers\"],\"id\":1,\"jsonrpc\":\"2.0\"}"
sub = p.subscribe_ws(ws_query)

# Print the first 5 events received
for e in islice(sub.events, 5):
print('subscription event', e)

# Unsubscribe to the event
sub.unsubscribe()

13 changes: 13 additions & 0 deletions parity-clib/examples/python/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash

mkdir -p ./venv
python3 -m venv ./venv

source ./venv/bin/activate

pushd ../../python
pip install -r requirements.txt
python setup.py install
popd

python example.py
5 changes: 5 additions & 0 deletions parity-clib/python/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.egg-info/
__pycache__/
build/
dist/
venv/
121 changes: 121 additions & 0 deletions parity-clib/python/parity/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Copyright 2019 Parity Technologies (UK) Ltd.
# This file is part of Parity.
#
# Parity is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Parity is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Parity. If not, see <http://www.gnu.org/licenses/>.

from queue import Queue

# Rust extension module, see src/python.rs
import _parity


class _CallbackGenerator(object):
"""Thin wrapper around a Queue which can be passed as a callback to _parity.* functions"""

def __init__(self):
self.queue = Queue(maxsize=1)

def __call__(self, value):
self.queue.put(value)

def __iter__(self):
return self

def __next__(self):
return self.get()

def get(self, block=True, timeout=None):
"""Get an element from the queue

:param block: Should we block if no element available
:param timeout: Time to wait for new element
:return: Top item from the queue
:raises: queue.Empty
"""
return self.queue.get(block, timeout)

def get_nowait(self):
"""Get an element from the queue, do not wait.

Equivalent to `get(False)`

:return: Top item from the queue
:raises: queue.Empty
"""
return self.queue.get_nowait()


class Subscription(object):
"""Encapsulates a subscription returned from subscribe_ws, allowing iteration over events and unsubscribing"""

def __init__(self, sub, events):
self._sub = sub
self.events = events

def unsubscribe(self):
"""Unsubscribe from the underlying subscription"""
self._sub.unsubscribe()


class Parity(object):
"""Connection to Parity client"""

def __init__(self, options, logger_mode='', logger_file=''):
"""Configure and start Parity

:param options: Command line arguments to pass to Parity
:param logger_mode: Logger options to pass to Parity
:param logger_file: File to log to
"""
config = _parity.config_from_cli(options)
self.handle = _parity.build(config, logger_mode, logger_file)

def rpc_query_async(self, query, cb, timeout_ms=1000):
"""Perform a RPC query, return immediately, cb will be invoked with the result asynchronously

:param query: Query to perform
:param cb: Callback to invoke with results
:param timeout_ms: Timeout in milliseconds
"""
_parity.rpc_query(self.handle, query, timeout_ms, cb)

def rpc_query_sync(self, query, timeout_ms=1000):
"""Perform a RPC query and return the result synchronously

:param query: Query to perform
:param timeout_ms: Timeout in milliseconds
:return: Result of the rpc call
"""
cb = _CallbackGenerator()
self.rpc_query_async(query, cb, timeout_ms)
return next(cb)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will call the iterator of the Callback class?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

of the _CallbackGenerator class, yes


def subscribe_ws_cb(self, query, cb):
"""Subscribe to a websocket event, return immediately, cb will be invoked with events asynchronously

:param query: Query to perform
:param cb: Callback to invoke with events
:return: Subscription handle
"""
return _parity.subscribe_ws(self.handle, query, cb)

def subscribe_ws(self, query):
"""Subscribe to a websocket event, return immediately, Subscription object can be iterated to receive events

:param query: Query to perform
:return: Subscription object which can be iterated over to receive events
"""
cb = _CallbackGenerator()
sub = self.subscribe_ws_cb(query, cb)
return Subscription(sub, cb)
1 change: 1 addition & 0 deletions parity-clib/python/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
setuptools-rust>=0.10.2
39 changes: 39 additions & 0 deletions parity-clib/python/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Based off example setup.py from pyo3
import sys

from setuptools import setup
from setuptools.command.test import test as TestCommand
from setuptools_rust import RustExtension

def get_py_version_cfgs():
# For now each Cfg Py_3_X flag is interpreted as "at least 3.X"
version = sys.version_info[0:2]

if version[0] == 2:
return ["--cfg=Py_2"]

py3_min = 5
out_cfg = []
for minor in range(py3_min, version[1] + 1):
out_cfg.append("--cfg=Py_3_%d" % minor)

return out_cfg


install_requires = []
tests_require = install_requires + ["pytest", "pytest-benchmark"]

setup(
name="parity-clib",
version="2.5.0",
packages=["parity"],
rust_extensions=[
RustExtension(
"_parity", "../Cargo.toml", rustc_flags=get_py_version_cfgs()
),
],
install_requires=install_requires,
tests_require=tests_require,
include_package_data=True,
zip_safe=False,
)
7 changes: 7 additions & 0 deletions parity-clib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ extern crate jni;
#[cfg(feature = "jni")]
mod java;

#[cfg(feature = "pyo3")]
#[macro_use]
extern crate pyo3;

#[cfg(feature = "pyo3")]
mod python;

use std::ffi::CString;
use std::os::raw::{c_char, c_void, c_int};
use std::{panic, ptr, slice, str, thread};
Expand Down
Loading