Tutorial: Python Package
This tutorial walks through building a Rust library, generating Python
ctypes bindings with WeaveFFI, and packaging it for pip install.
Prerequisites
- Rust toolchain (stable channel)
- Python 3.7+
- WeaveFFI CLI installed (
cargo install weaveffi-cli)
1) Define your API
Create a file called greeter.yml:
version: "0.1.0"
modules:
- name: greeter
structs:
- name: Greeting
fields:
- { name: message, type: string }
- { name: lang, type: string }
functions:
- name: hello
params:
- { name: name, type: string }
return: string
- name: greeting
params:
- { name: name, type: string }
- { name: lang, type: string }
return: Greeting
2) Generate bindings
weaveffi generate greeter.yml -o generated --scaffold
This produces (among other targets):
generated/
├── c/
│ └── weaveffi.h
├── python/
│ ├── pyproject.toml
│ ├── setup.py
│ ├── README.md
│ └── weaveffi/
│ ├── __init__.py
│ ├── weaveffi.py
│ └── weaveffi.pyi
└── scaffold.rs
The generated Python package uses ctypes — no native extension
compilation is needed on the Python side.
3) Create the Rust library
cargo init --lib mygreeter
mygreeter/Cargo.toml:
[package]
name = "mygreeter"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
weaveffi-abi = { version = "0.1" }
mygreeter/src/lib.rs:
#![allow(unused)]
#![allow(unsafe_code)]
#![allow(clippy::not_unsafe_ptr_arg_deref)]
fn main() {
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use weaveffi_abi::{self as abi, weaveffi_error};
#[no_mangle]
pub extern "C" fn weaveffi_greeter_hello(
name_ptr: *const c_char,
_name_len: usize,
out_err: *mut weaveffi_error,
) -> *const c_char {
abi::error_set_ok(out_err);
let name = unsafe { CStr::from_ptr(name_ptr) }.to_str().unwrap_or("world");
let msg = format!("Hello, {name}!");
CString::new(msg).unwrap().into_raw() as *const c_char
}
#[no_mangle]
pub extern "C" fn weaveffi_free_string(ptr: *const c_char) {
abi::free_string(ptr);
}
#[no_mangle]
pub extern "C" fn weaveffi_free_bytes(ptr: *mut u8, len: usize) {
abi::free_bytes(ptr, len);
}
#[no_mangle]
pub extern "C" fn weaveffi_error_clear(err: *mut weaveffi_error) {
abi::error_clear(err);
}
}
Fill in the remaining functions using scaffold.rs as a guide.
4) Build the shared library
cargo build -p mygreeter --release
This produces the shared library:
| Platform | Output |
|---|---|
| macOS | target/release/libmygreeter.dylib |
| Linux | target/release/libmygreeter.so |
| Windows | target/release/mygreeter.dll |
5) Install the Python package
cd generated/python
pip install .
For development iteration, use an editable install:
pip install -e .
6) Make the shared library findable
The generated ctypes loader looks for libweaveffi.dylib (macOS),
libweaveffi.so (Linux), or weaveffi.dll (Windows) on the system
library search path.
Rename or symlink your library to match the expected name, then set the library path:
macOS:
cp target/release/libmygreeter.dylib target/release/libweaveffi.dylib
DYLD_LIBRARY_PATH=target/release python your_script.py
Linux:
cp target/release/libmygreeter.so target/release/libweaveffi.so
LD_LIBRARY_PATH=target/release python your_script.py
Windows:
Place weaveffi.dll in the same directory as your script or add its
directory to PATH.
Alternatively, for production, copy the shared library into the Python
package directory and adjust the loader path in weaveffi.py.
7) Use the bindings
Create a script called demo.py:
from weaveffi import hello, greeting
msg = hello("Python")
print(msg) # "Hello, Python!"
g = greeting("Python", "en")
print(f"{g.message} ({g.lang})")
Run it:
DYLD_LIBRARY_PATH=target/release python demo.py # macOS
LD_LIBRARY_PATH=target/release python demo.py # Linux
Error handling
WeaveFFI errors are raised as WeaveffiError exceptions:
from weaveffi import WeaveffiError
try:
result = hello("")
except WeaveffiError as e:
print(f"Error {e.code}: {e.message}")
Struct lifecycle
Struct wrappers automatically free Rust memory when garbage collected. For explicit control, delete the reference:
g = greeting("Python", "en")
print(g.message)
del g # immediately calls the Rust destroy function
8) Type stubs and IDE support
The generated weaveffi.pyi stub file provides type information for
editors and mypy:
mypy demo.py
IDEs like VS Code and PyCharm will show autocomplete for all generated functions, classes, and properties.
Troubleshooting
| Problem | Solution |
|---|---|
OSError: dlopen ... not found | The shared library is not on the library search path. Set DYLD_LIBRARY_PATH or LD_LIBRARY_PATH. |
WeaveffiError at runtime | A Rust-side error was returned. Check the error code and message. |
ModuleNotFoundError: No module named 'weaveffi' | Run pip install . from generated/python/. |
| mypy type errors | Ensure weaveffi.pyi is in the package directory alongside weaveffi.py. |
Next steps
- See the Python generator reference for type mapping and memory management details.
- Read the Error Handling guide for the full error model.
- Explore the Calculator tutorial for a simpler end-to-end walkthrough.