Skip to content

Commit

Permalink
Merge pull request #138 from wenjing/python-interface-prototype
Browse files Browse the repository at this point in the history
python interface prototype
  • Loading branch information
tweedegolf-marc authored Jun 7, 2024
2 parents d1fb963 + 3cf2159 commit a88cb76
Show file tree
Hide file tree
Showing 6 changed files with 342 additions and 1 deletion.
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = [ "tsp", "examples", "fuzz" ]
members = [ "tsp", "examples", "fuzz" , "tsp-python"]

[workspace.package]
version = "0.1.0"
Expand Down Expand Up @@ -45,3 +45,5 @@ arbitrary = { version = "1.0", features = ["derive"] }
clap = { version = "4.5", features = ["derive"] }
# demo server
axum = { version = "0.7", features = ["ws"] }

tsp = { path = "./tsp" }
138 changes: 138 additions & 0 deletions tsp-python/.github/workflows/CI.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# This file is autogenerated by maturin v1.5.1
# To update, run
#
# maturin generate-ci github
#
name: CI

on:
push:
branches:
- main
- master
tags:
- '*'
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
linux:
runs-on: ${{ matrix.platform.runner }}
strategy:
matrix:
platform:
- runner: ubuntu-latest
target: x86_64
- runner: ubuntu-latest
target: x86
- runner: ubuntu-latest
target: aarch64
- runner: ubuntu-latest
target: armv7
- runner: ubuntu-latest
target: s390x
- runner: ubuntu-latest
target: ppc64le
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --find-interpreter
sccache: 'true'
manylinux: auto
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-linux-${{ matrix.platform.target }}
path: dist

windows:
runs-on: ${{ matrix.platform.runner }}
strategy:
matrix:
platform:
- runner: windows-latest
target: x64
- runner: windows-latest
target: x86
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.10'
architecture: ${{ matrix.platform.target }}
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --find-interpreter
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-windows-${{ matrix.platform.target }}
path: dist

macos:
runs-on: ${{ matrix.platform.runner }}
strategy:
matrix:
platform:
- runner: macos-latest
target: x86_64
- runner: macos-14
target: aarch64
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --find-interpreter
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-macos-${{ matrix.platform.target }}
path: dist

sdist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build sdist
uses: PyO3/maturin-action@v1
with:
command: sdist
args: --out dist
- name: Upload sdist
uses: actions/upload-artifact@v4
with:
name: wheels-sdist
path: dist

release:
name: Release
runs-on: ubuntu-latest
if: "startsWith(github.ref, 'refs/tags/')"
needs: [linux, windows, macos, sdist]
steps:
- uses: actions/download-artifact@v4
- name: Publish to PyPI
uses: PyO3/maturin-action@v1
env:
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
with:
command: upload
args: --non-interactive --skip-existing wheels-*/*
72 changes: 72 additions & 0 deletions tsp-python/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/target

# Byte-compiled / optimized / DLL files
__pycache__/
.pytest_cache/
*.py[cod]

# C extensions
*.so

# Distribution / packaging
.Python
.venv/
env/
bin/
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
include/
man/
venv/
*.egg-info/
.installed.cfg
*.egg

# Installer logs
pip-log.txt
pip-delete-this-directory.txt
pip-selfcheck.json

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml

# Translations
*.mo

# Mr Developer
.mr.developer.cfg
.project
.pydevproject

# Rope
.ropeproject

# Django stuff:
*.log
*.pot

.DS_Store

# Sphinx documentation
docs/_build/

# PyCharm
.idea/

# VSCode
.vscode/

# Pyenv
.python-version
15 changes: 15 additions & 0 deletions tsp-python/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "tsp-python"
version = "0.1.0"
edition = "2021"
license.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "tsp_python"
crate-type = ["cdylib"]

[dependencies]
pyo3 = { version = "0.21.2", features = ["experimental-async"] }
tsp.workspace = true
tokio.workspace = true
15 changes: 15 additions & 0 deletions tsp-python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[build-system]
requires = ["maturin>=1.5,<2.0"]
build-backend = "maturin"

[project]
name = "tsp-python"
requires-python = ">=3.8"
classifiers = [
"Programming Language :: Rust",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
dynamic = ["version"]
[tool.maturin]
features = ["pyo3/extension-module"]
99 changes: 99 additions & 0 deletions tsp-python/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use std::future::Future;

use pyo3::{exceptions::PyException, prelude::*};

fn tokio() -> &'static tokio::runtime::Runtime {
use std::sync::OnceLock;
static RT: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
RT.get_or_init(|| tokio::runtime::Runtime::new().unwrap())
}

fn py_exception<E: std::fmt::Debug>(e: E) -> PyErr {
PyException::new_err(format!("{e:?}"))
}

#[pyclass]
struct AsyncStore(tsp::Store);

async fn spawn<F>(fut: F) -> PyResult<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
tokio().spawn(fut).await.map_err(py_exception)
}

#[pymethods]
impl AsyncStore {
#[new]
fn new() -> Self {
Self(tsp::Store::default())
}

fn add_private_vid(&self, vid: OwnedVid) -> PyResult<()> {
self.0.add_private_vid(vid.0).unwrap();
Ok(())
}

async fn verify_vid(&mut self, vid: String) -> PyResult<()> {
let verified_vid = spawn(async move { tsp::vid::verify_vid(&vid).await })
.await?
.map_err(py_exception)?;

self.0.add_verified_vid(verified_vid).map_err(py_exception)
}

#[pyo3(signature = (sender, receiver, nonconfidential_data, message))]
async fn send(
&self,
sender: String,
receiver: String,
nonconfidential_data: Option<Vec<u8>>,
message: Vec<u8>,
) -> PyResult<Vec<u8>> {
let (url, bytes) = self
.0
.seal_message(
&sender,
&receiver,
nonconfidential_data.as_deref(),
&message,
)
.map_err(py_exception)?;

let fut = async move {
tsp::transport::send_message(&url, &bytes).await?;
Ok::<Vec<_>, tsp::transport::TransportError>(bytes)
};

spawn(fut).await?.map_err(py_exception)
}
}

#[pyclass]
#[derive(Clone)]
struct OwnedVid(tsp::OwnedVid);

#[pymethods]
impl OwnedVid {
#[staticmethod]
async fn from_file(path: String) -> PyResult<OwnedVid> {
let fut = async move {
let owned_vid = tsp::OwnedVid::from_file(&path)
.await
.map_err(py_exception)?;
Ok(Self(owned_vid))
};

tokio().spawn(fut).await.unwrap()
}
}

/// A Python module implemented in Rust.
#[pymodule]
fn tsp_python(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<AsyncStore>()?;
m.add_class::<OwnedVid>()?;

Ok(())
}

0 comments on commit a88cb76

Please sign in to comment.