33
loading...
This website collects cookies to deliver better user experience
cargo
and venv
.cargo new --lib my-python-lib
cd my-python-lib
python -m venv venv
source venv/bin/activate
my-python-lib
, then sets-up a Python virtual environment.Note: You'll need to have the rust toolchain and python 3.6 or above installed.
Cargo.toml
:[lib]
crate-type = ["cdylib"]
[dependencies]
cpython = { version = "0.5", features = ["extension-module"] }
[build-dependencies]
flapigen = "0.6.0-pre7"
crate-type = ["cdylib"]
tells Rust to build our crate as a C-compatible dynamic library rather than a typical crate. This crate type will allow our Python code to interact with our library as if it were compiled C code rather than Rust.use flapigen::{LanguageConfig, PythonConfig};
use std::{env, path::Path};
fn main() {
let in_src = Path::new("src").join("glue.rs.in");
let out_dir = env::var("OUT_DIR").unwrap();
let out_src = Path::new(&out_dir).join("glue.rs");
let python_cfg = PythonConfig::new("my_python_lib".to_owned());
let flap_gen =
flapigen::Generator::new(LanguageConfig::PythonConfig(python_cfg)).rustfmt_bindings(true);
flap_gen.expand("python bindings", &in_src, &out_src);
println!("cargo:rerun-if-changed={}", in_src.display());
}
flapigen
to run on our project. At build time, it will read the "glue code" we write in src/glue.rs.in
, and generate Rust code to interact with Python and place it in ${OUT_DIR}/glue.rs
.src/glue.rs.in
file with something like the following:pub struct Foo {
val: i32
}
impl Foo {
pub fn new(val: i32) -> Self {
Self {
val
}
}
pub fn set_field(&mut self, new_val: i32) {
self.val = new_val;
}
pub fn val(&self) -> i32 {
self.val
}
}
foreign_class!(class Foo {
self_type Foo;
constructor Foo::new(_: i32) -> Foo;
fn Foo::set_field(&mut self, _: i32);
fn Foo::val(&self) -> i32;
});
src/lib.rs
should currently have some basic tests. We'll change it to the following:#![allow(non_snake_case, unused)]
include!(concat!(env!("OUT_DIR"), "/glue.rs"));
${OUT_DIR}/glue.rs
and includes the contents into src/lib.rs
in the build directory. The result will be as if we hand-wrote the generated code inlib.rs
file.foreign_class
macro into many cpython functions as an extension module, and cargo compiles it as a cdylib
. If you want to see what that looks like, install cargo-expand
and run cargo expand
. You'll get a lot of generated rust code.setup
, we created a virtual environment, and now we'll need to install some Python tools via:$ source venv/bin/activate && pip install setuptools-rust
setup.py
with:from setuptools import setup
from setuptools_rust import Binding, RustExtension
setup(
name="my-python-lib",
version="1.0",
rust_extensions=[RustExtension("my_python_lib", binding=Binding.RustCPython)],
# rust extensions are not zip safe, just like C-extensions.
zip_safe=False,
)
RustCPython
as the binding.python setup.py develop
. Python calls cargo
and moves cdylib
into your local directory.simple.py
script with the following in it:from my_python_lib import Foo
foo = Foo(1)
print(foo.val())
foo.set_field(11)
print(foo.val())
python simple.py
should result in:$ python simple.py
1
11
_fluvio_python
; a private python module that wraps the rust structs with python classes, giving us nice documentation generation.pypi
is beyond the scope of this blog. Checkout the Makefile
and the github publishing workflow for additional information.