├── .gitignore ├── .travis.yml ├── Cargo.toml ├── README.md ├── build.rs └── src ├── base.rs ├── ffi.rs ├── macroexpand.c ├── primtypes.rs └── rustpy.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Compiled Dynamic libraries 8 | *.so 9 | *.dylib 10 | *.dll 11 | 12 | # Compiled Static libraries 13 | *.lai 14 | *.la 15 | *.a 16 | *.lib 17 | 18 | # Executables 19 | *.exe 20 | *.out 21 | *.app 22 | *.rlib 23 | target 24 | 25 | # Cargo 26 | Cargo.lock 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | script: 3 | - cargo build 4 | - cargo test 5 | - rustdoc src/rustpy.rs 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "rustpy" 4 | version = "0.1.0" 5 | authors = [ "luke.s.metz@gmail.com" ] 6 | build = "build.rs" 7 | 8 | [lib] 9 | name = "rustpy" 10 | 11 | [dependencies] 12 | libc = "0.1.8" 13 | lazy_static = "0.1.11" 14 | 15 | [build-dependencies] 16 | gcc = "0.3.8" 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Rustpy 2 | ===== 3 | 4 | A simple library to allow for easy use of python from rust. 5 | 6 | ## Status 7 | Currently this library has not received much love (pull requests welcome for any interested) and does not build with rust 1.0. 8 | 9 | For another library that also strives to bridge the gap between python and rust and might be a little more up to day see: 10 | 11 | https://github.com/dgrunwald/rust-python27-sys 12 | 13 | https://github.com/dgrunwald/rust-cpython 14 | 15 | ## How to Use 16 | 17 | This library is meant to be middle ware for users wanting to use 18 | python libraries from rust. It allows users to quickly use existing 19 | tools and get working on interesting things fast! 20 | 21 | See [pysmtplib.rs](https://github.com/lukemetz/pysmtplib.rs) for an 22 | example of how to bind enough smtplib to send emails. 23 | 24 | 25 | For more documentation, run `rustdoc src/rustpy.rs` and look at 26 | doc/rustpy/index.html. Pull requests are welcome! 27 | 28 | ```rust 29 | extern crate rustpy; 30 | use rustpy::{ToPyType, FromPyType, PyState}; 31 | 32 | fn main() { 33 | let py = PyState::new(); 34 | let module = py.get_module("math").unwrap(); 35 | let func = module.get_func("sqrt").unwrap(); 36 | let args = (144f32, ).to_py_object(&py).unwrap(); 37 | let untyped_res = func.call(&args).unwrap(); 38 | let result = py.from_py_object::(untyped_res).unwrap(); 39 | assert_eq!(result, 12f32); 40 | } 41 | ``` 42 | Important note: Only create one instance of PyState at a time. 43 | On construction, it grabs a global lock to prevent more than one thread from 44 | interacting with the interpreter thus making it very easy to deadlock. 45 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate gcc; 2 | 3 | fn main() { 4 | gcc::compile_library("libmacroexpand.a", &["src/macroexpand.c"]); 5 | } 6 | -------------------------------------------------------------------------------- /src/base.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Mutex, MutexGuard}; 2 | use std::ptr; 3 | use std::marker::PhantomData; 4 | use std::mem::transmute; 5 | use std::ffi::CString; 6 | use std::fmt; 7 | pub use ffi::{PythonCAPI, PyObjectRaw}; 8 | 9 | lazy_static! { 10 | static ref PY_MUTEX: Mutex<()> = Mutex::new(()); 11 | } 12 | 13 | /// Struct to control interaction with the python interpreter. 14 | /// 15 | /// There can only be one active PyState at a time, as on initialization 16 | /// a shared mutex gets locked. This allows for safe-ish execution of 17 | /// python at the cost of increased risk of deadlocks. 18 | pub struct PyState { 19 | #[allow(dead_code)] 20 | guard: MutexGuard<'static, ()>, 21 | } 22 | 23 | impl PyState { 24 | /// Get a new instance of the python interpreter. 25 | pub fn new() -> PyState { 26 | unsafe { 27 | let guard = PY_MUTEX.lock(); 28 | let state = PyState { guard: guard.unwrap() }; 29 | state.Py_Initialize(); 30 | state 31 | } 32 | } 33 | 34 | /// Return the PyObject at the associated name. Will `Err` if no module found. 35 | pub fn get_module<'a>(&'a self, module_name: &str) -> Result, PyError> { 36 | unsafe { 37 | let string = CString::new(module_name).unwrap(); 38 | let py_module = self.PyImport_ImportModule(string.as_ptr()); 39 | 40 | let exception = self.get_result_exception(); 41 | 42 | if exception.is_err() { 43 | Err(exception.err().unwrap()) 44 | } else if !py_module.is_null() { 45 | Ok(PyObject::new(self, py_module)) 46 | } else { 47 | Err(PyError::NullPyObject) 48 | } 49 | } 50 | } 51 | 52 | /// Helper function to convert `PyObject` back to rust types. 53 | pub fn from_py_object(&self, obj: PyObject) -> Result { 54 | FromPyType::from_py_object(self, obj) 55 | } 56 | 57 | /// Low level function to check for python inturpreter errors 58 | pub fn get_result_exception(&self) -> Result<(), PyError> { 59 | unsafe { 60 | let ptype: *mut PyObjectRaw = ptr::null_mut(); 61 | let pvalue: *mut PyObjectRaw = ptr::null_mut(); 62 | let ptraceback: *mut PyObjectRaw = ptr::null_mut(); 63 | self.PyErr_Fetch(transmute(&ptype), 64 | transmute(&pvalue), 65 | transmute(&ptraceback)); 66 | self.PyErr_NormalizeException(transmute(&ptype), 67 | transmute(&pvalue), 68 | transmute(&ptraceback)); 69 | if pvalue.is_null() { 70 | Ok(()) 71 | } else { 72 | let base = PyObject::new(self, self.PyObject_Str(pvalue)); 73 | let error_type_string = self.PyObject_GetAttrString(ptype, 74 | CString::new("__name__") 75 | .unwrap() 76 | .as_ptr()); 77 | let error_type = PyObject::new(self, error_type_string); 78 | let base_string = self.from_py_object::(base).unwrap(); 79 | let error_type_string = self.from_py_object::(error_type).unwrap(); 80 | Err(PyError::PyException(error_type_string + " : " + &base_string)) 81 | } 82 | } 83 | } 84 | } 85 | 86 | /// Wrapper around python PyObject. 87 | pub struct PyObject<'a> { 88 | pub state: &'a PyState, 89 | pub raw: *mut PyObjectRaw, 90 | } 91 | 92 | impl<'a> PyObject<'a> { 93 | /// Wrap a raw PyObject pointer. Should not be called by user 94 | pub fn new(state: &'a PyState, py_object_raw: *mut PyObjectRaw) -> PyObject<'a> { 95 | assert!(!py_object_raw.is_null()); 96 | PyObject { 97 | state: state, 98 | raw: py_object_raw, 99 | } 100 | } 101 | 102 | /// Constructor for empty PyObject tuple for void functions 103 | pub fn empty_tuple(state: &'a PyState) -> PyObject<'a> { 104 | unsafe { 105 | let raw = state.PyTuple_New(0); 106 | PyObject::new(state, raw) 107 | } 108 | } 109 | 110 | /// Get PyObject corresponding to a function 111 | pub fn get_func(&self, string: &str) -> Result, PyError> { 112 | self.get_member_obj(string) 113 | } 114 | 115 | /// Get a member variable as PyObject 116 | pub fn get_member_obj(&self, name: &str) -> Result, PyError> { 117 | unsafe { 118 | let py_member = self.state.PyObject_GetAttrString(self.raw, 119 | CString::new(name).unwrap().as_ptr()); 120 | let exception = self.state.get_result_exception(); 121 | if exception.is_err() { 122 | Err(exception.err().unwrap()) 123 | } else if py_member.is_null() { 124 | Err(PyError::NullPyObject) 125 | } else { 126 | Ok(PyObject::new(self.state, py_member)) 127 | } 128 | } 129 | } 130 | 131 | /// Get member variable as native type 132 | pub fn get_member(&self, name: &str) -> Result { 133 | self.get_member_obj(name).and_then(|x| self.state.from_py_object(x)) 134 | } 135 | 136 | /// Call a PyObject with the tuple provided in `args` 137 | pub fn call(&self, args: &PyObject) -> Result, PyError> { 138 | unsafe { 139 | let py_ret = self.state.PyObject_CallObject(self.raw, args.raw); 140 | let exception = self.state.get_result_exception(); 141 | if exception.is_err() { 142 | Err(exception.err().unwrap()) 143 | } else if py_ret.is_null() { 144 | Err(PyError::NullPyObject) 145 | } else { 146 | Ok(PyObject::new(self.state, py_ret)) 147 | } 148 | } 149 | } 150 | 151 | /// Helper function to call returning type 152 | pub fn call_with_ret<'b, T: FromPyType>(&'b self, args: &PyObject) -> Result { 153 | self.call(args).and_then(|x| self.state.from_py_object::(x)) 154 | } 155 | 156 | pub fn call_func<'b, I: ToPyType>(&'b self, 157 | name: &str, 158 | args: I) 159 | -> Result, PyError> { 160 | self.get_func(name) 161 | .and_then(|x| args.to_py_object(self.state).and_then(|input| x.call(&input))) 162 | } 163 | 164 | pub fn call_func_with_ret(&self, 165 | name: &str, 166 | args: I) 167 | -> Result { 168 | self.get_func(name) 169 | .and_then(|x| args.to_py_object(self.state).and_then(|input| x.call_with_ret(&input))) 170 | } 171 | 172 | fn get_iter<'b>(&'b self) -> Result, PyError> { 173 | unsafe { 174 | let py_iter = self.state.PyObject_GetIter(self.raw); 175 | if py_iter.is_null() { 176 | Err(PyError::NullPyObject) 177 | } else { 178 | Ok(PyObject::new(self.state, py_iter)) 179 | } 180 | } 181 | } 182 | 183 | /// Get a Rust iterator wrapping a Python iterator. 184 | /// 185 | /// Returns an error if this object does not implement the Python 186 | /// iterator protocol. 187 | pub fn iter<'b, T: FromPyType>(&'b self) -> Result, PyError> { 188 | self.get_iter().and_then(|py_object| PyIterator::new(py_object)) 189 | } 190 | } 191 | 192 | impl<'a> Drop for PyObject<'a> { 193 | fn drop(&mut self) { 194 | unsafe { 195 | self.state.Py_DecRef(self.raw); 196 | } 197 | } 198 | } 199 | 200 | // TODO this seems unsafe / bad. Should probably shift to ARC or something 201 | impl<'a> Clone for PyObject<'a> { 202 | fn clone(&self) -> PyObject<'a> { 203 | unsafe { 204 | self.state.Py_IncRef(self.raw); 205 | } 206 | PyObject::new(self.state, self.raw.clone()) 207 | } 208 | } 209 | 210 | impl<'a> fmt::Debug for PyObject<'a> { 211 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 212 | unsafe { 213 | let string = self.state.PyObject_Str(self.raw); 214 | let result = self.state 215 | .from_py_object::(PyObject::new(self.state, string)) 216 | .unwrap(); 217 | write!(fmt, "PyObject{{{}}}", result) 218 | } 219 | } 220 | } 221 | 222 | /// Possible errors while using rustpy 223 | /// 224 | /// Generally speaking, all errors are from this library or user 225 | /// interaction with this library such as passing in wrong types of PyObject. 226 | /// The PyExecption error is an exception from python that causes a function or 227 | /// operation to fail. 228 | #[derive(Debug)] 229 | pub enum PyError { 230 | FromTypeConversionError, 231 | ToTypeConversionError, 232 | StringConversionError, 233 | PyException(String), 234 | NullPyObject, 235 | NotAnIterator, 236 | } 237 | 238 | /// Rust type that can be converted to a Python object 239 | pub trait ToPyType { 240 | fn to_py_object<'a>(&'a self, state: &'a PyState) -> Result, PyError>; 241 | } 242 | 243 | /// Rust type that can be extracted from a Python object 244 | pub trait FromPyType { 245 | fn from_py_object<'a>(state: &'a PyState, py_object: PyObject<'a>) -> Result 246 | where Self: Sized; 247 | } 248 | 249 | /// Wrapper around a Python iterator 250 | pub struct PyIterator<'a, T> { 251 | py_object: PyObject<'a>, 252 | _phantom: PhantomData, 253 | } 254 | 255 | impl<'a, T: FromPyType> PyIterator<'a, T> { 256 | /// Create Rust iterator from a Python object implementing the 257 | /// iterator protocol. 258 | pub fn new(obj: PyObject<'a>) -> Result, PyError> { 259 | unsafe { 260 | if obj.state.PyIter_Check(obj.raw) != 0 { 261 | Ok(PyIterator { 262 | py_object: obj, 263 | _phantom: PhantomData, 264 | }) 265 | } else { 266 | Err(PyError::NotAnIterator) 267 | } 268 | } 269 | } 270 | 271 | fn next_py_object(&mut self) -> Option> { 272 | unsafe { 273 | let py_next = self.py_object.state.PyIter_Next(self.py_object.raw); 274 | if py_next.is_null() { 275 | None 276 | } else { 277 | Some(PyObject::new(self.py_object.state, py_next)) 278 | } 279 | } 280 | } 281 | } 282 | 283 | impl<'a, T: FromPyType> Iterator for PyIterator<'a, T> { 284 | type Item = Result; 285 | fn next(&mut self) -> Option> { 286 | self.next_py_object() 287 | .and_then(|py_object| Some(self.py_object.state.from_py_object(py_object))) 288 | } 289 | } 290 | 291 | #[cfg(test)] 292 | mod test { 293 | use super::PyState; 294 | use primtypes::{ToPyType, FromPyType, PyObject}; 295 | use super::PyError; 296 | macro_rules! try_or_panic ( 297 | ($e:expr) => (match $e { Ok(e) => e, Err(e) => panic!("{:?}", e) }) 298 | ); 299 | 300 | #[test] 301 | fn test_empty_tuple_should_not_fail() { 302 | let py = PyState::new(); 303 | let _ = PyObject::empty_tuple(&py); 304 | } 305 | 306 | #[test] 307 | fn test_get_module() { 308 | let py = PyState::new(); 309 | let pyobj = py.get_module("math"); 310 | match pyobj { 311 | Err(_) => panic!("Failed to import math"), 312 | Ok(x) => assert!(!x.raw.is_null()), 313 | } 314 | } 315 | 316 | #[test] 317 | fn math_sqrt() { 318 | let py = PyState::new(); 319 | let module = try_or_panic!(py.get_module("math")); 320 | let func = try_or_panic!(module.get_func("sqrt")); 321 | let input = (144f32,); 322 | let arg = try_or_panic!(input.to_py_object(&py)); 323 | let py_result = try_or_panic!(func.call(&arg)); 324 | let result = try_or_panic!(py.from_py_object::(py_result)); 325 | assert_eq!(result, 12f32); 326 | } 327 | 328 | #[test] 329 | fn math_pow() { 330 | let py = PyState::new(); 331 | let module = try_or_panic!(py.get_module("math")); 332 | let func = try_or_panic!(module.get_func("pow")); 333 | let input = (3f32, 2f32); 334 | let arg = try_or_panic!(input.to_py_object(&py)); 335 | let py_result = try_or_panic!(func.call(&arg)); 336 | let result = try_or_panic!(py.from_py_object::(py_result)); 337 | assert_eq!(result, 9f32); 338 | } 339 | 340 | #[test] 341 | fn test_exceptions_module() { 342 | let py = PyState::new(); 343 | let module = py.get_module("mathSpelledWrong"); 344 | match module { 345 | Ok(_) => panic!("Did not return Err"), 346 | Err(PyError::PyException(s)) => { 347 | assert_eq!(&s, "ImportError : No module named mathSpelledWrong") 348 | } 349 | Err(e) => panic!("Got unexpected error: {:?}", e), 350 | }; 351 | } 352 | 353 | #[test] 354 | fn test_exceptions_function_lookup() { 355 | let py = PyState::new(); 356 | let module = try_or_panic!(py.get_module("math")); 357 | let func = module.get_func("powMissSpelled"); 358 | match func { 359 | Ok(_) => panic!("Did not return Err"), 360 | Err(PyError::PyException(s)) => { 361 | assert_eq!(&s, 362 | "AttributeError : 'module' object has no attribute 'powMissSpelled'") 363 | } 364 | Err(e) => panic!("Got unexpected error: {:?}", e), 365 | }; 366 | } 367 | 368 | #[test] 369 | fn test_exceptions_function_call() { 370 | let py = PyState::new(); 371 | let module = try_or_panic!(py.get_module("math")); 372 | let func = try_or_panic!(module.get_func("pow")); 373 | let input = (3f32, 2f32, 314i32); 374 | let badarg = try_or_panic!(input.to_py_object(&py)); 375 | let res = func.call(&badarg); 376 | match res { 377 | Ok(_) => panic!("Did not return Err"), 378 | Err(PyError::PyException(s)) => { 379 | assert_eq!(&s, "TypeError : pow expected 2 arguments, got 3") 380 | } 381 | Err(e) => panic!("Got unexpected error: {:?}", e), 382 | }; 383 | } 384 | 385 | #[test] 386 | fn test_call_with_ret() { 387 | let py = PyState::new(); 388 | let module = try_or_panic!(py.get_module("math")); 389 | let func = try_or_panic!(module.get_func("pow")); 390 | let input = (3f32, 2f32); 391 | let arg = try_or_panic!(input.to_py_object(&py)); 392 | let result = try_or_panic!(func.call_with_ret::(&arg)); 393 | assert_eq!(result, 9f32); 394 | } 395 | 396 | #[test] 397 | fn test_call_func() { 398 | let py = PyState::new(); 399 | let module = try_or_panic!(py.get_module("math")); 400 | let obj = try_or_panic!(module.call_func("pow", (3f32, 2f32))); 401 | let result = try_or_panic!(py.from_py_object::(obj)); 402 | 403 | assert_eq!(result, 9f32); 404 | } 405 | 406 | #[test] 407 | fn test_call_func_with_ret() { 408 | let py = PyState::new(); 409 | let module = try_or_panic!(py.get_module("math")); 410 | let result: f32 = try_or_panic!(module.call_func_with_ret("pow", (3f32, 2f32))); 411 | assert_eq!(result, 9f32); 412 | } 413 | 414 | #[test] 415 | fn test_get_member() { 416 | let py = PyState::new(); 417 | let module = try_or_panic!(py.get_module("math")); 418 | let result: f32 = try_or_panic!(module.get_member("pi")); 419 | assert!(result - 3.141593 < 0.001); 420 | } 421 | 422 | #[test] 423 | fn test_py_object_show() { 424 | let py = PyState::new(); 425 | let tup = (1, 2f32); 426 | assert_eq!(format!("{:?}", tup.to_py_object(&py).unwrap()), 427 | "PyObject{(1, 2.0)}".to_string()); 428 | } 429 | 430 | #[test] 431 | fn iterate_list() { 432 | let val = vec![1, 2, 3]; 433 | let py = PyState::new(); 434 | let py_object = try_or_panic!(val.to_py_object(&py)); 435 | let returned = try_or_panic!(py_object.iter()).map(|x| x.unwrap()).collect::>(); 436 | assert_eq!(returned, val); 437 | } 438 | 439 | #[test] 440 | fn iterate_combinations() { 441 | let py = PyState::new(); 442 | let itertools = try_or_panic!(py.get_module("itertools")); 443 | let comb = try_or_panic!(itertools.call_func("combinations", (vec![1, 2, 3, 4], 2))); 444 | let result = try_or_panic!(comb.iter()) 445 | .map(|x| x.unwrap()) 446 | .collect::>(); 447 | assert_eq!(vec![(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)], result); 448 | } 449 | 450 | #[test] 451 | fn iterate_count() { 452 | let py = PyState::new(); 453 | let itertools = try_or_panic!(py.get_module("itertools")); 454 | let perm = try_or_panic!(itertools.call_func("combinations", (vec![1, 2, 3, 4], 2))); 455 | let count = try_or_panic!(perm.iter::<(isize, isize)>()).count(); 456 | assert_eq!(6, count); 457 | } 458 | } 459 | -------------------------------------------------------------------------------- /src/ffi.rs: -------------------------------------------------------------------------------- 1 | use libc::{c_long, c_double, size_t, c_char}; 2 | use base::PyState; 3 | 4 | 5 | /// Wrapper around the PyObject pointer that the python capi uses. 6 | #[derive(Debug)] 7 | #[repr(C)] 8 | pub struct PyObjectRaw; 9 | 10 | #[link(name = "python2.7")] 11 | extern "C" { 12 | fn Py_Initialize(); 13 | fn Py_Finalize(); 14 | 15 | fn PyImport_ImportModule(name: *const c_char) -> *mut PyObjectRaw; 16 | 17 | fn Py_DecRef(obj: *mut PyObjectRaw); 18 | 19 | fn PyObject_CallObject(callable_object: *mut PyObjectRaw, 20 | args: *mut PyObjectRaw) 21 | -> *mut PyObjectRaw; 22 | fn PyObject_GetAttrString(object: *mut PyObjectRaw, attr: *const c_char) -> *mut PyObjectRaw; 23 | fn PyObject_Str(obj: *mut PyObjectRaw) -> *mut PyObjectRaw; 24 | fn PyObject_GetIter(obj: *mut PyObjectRaw) -> *mut PyObjectRaw; 25 | 26 | fn PyInt_FromLong(ival: c_long) -> *mut PyObjectRaw; 27 | fn PyInt_AsLong(obj: *mut PyObjectRaw) -> c_long; 28 | 29 | fn PyFloat_FromDouble(value: c_double) -> *mut PyObjectRaw; 30 | fn PyFloat_AsDouble(obj: *mut PyObjectRaw) -> c_double; 31 | 32 | fn PyTuple_New(size: size_t) -> *mut PyObjectRaw; 33 | fn PyTuple_GetItem(tuple: *mut PyObjectRaw, pos: size_t) -> *mut PyObjectRaw; 34 | fn PyTuple_SetItem(tuple: *mut PyObjectRaw, pos: size_t, o: *mut PyObjectRaw); 35 | fn PyTuple_Size(tuple: *mut PyObjectRaw) -> c_long; 36 | 37 | fn PyList_New(size: size_t) -> *mut PyObjectRaw; 38 | fn PyList_GetItem(list: *mut PyObjectRaw, index: size_t) -> *mut PyObjectRaw; 39 | fn PyList_SetItem(list: *mut PyObjectRaw, index: size_t, item: *mut PyObjectRaw); 40 | fn PyList_Size(list: *mut PyObjectRaw) -> c_long; 41 | 42 | fn PyString_FromString(string: *const c_char) -> *mut PyObjectRaw; 43 | fn PyString_AsString(obj: *mut PyObjectRaw) -> *const c_char; 44 | 45 | fn Py_IncRef(obj: *mut PyObjectRaw); 46 | 47 | fn PyErr_Fetch(ptype: *mut *mut PyObjectRaw, 48 | pvalue: *mut *mut PyObjectRaw, 49 | ptraceback: *mut *mut PyObjectRaw); 50 | fn PyErr_NormalizeException(ptype: *mut *mut PyObjectRaw, 51 | pvalue: *mut *mut PyObjectRaw, 52 | ptraceback: *mut *mut PyObjectRaw); 53 | 54 | fn PyIter_Next(obj: *mut PyObjectRaw) -> *mut PyObjectRaw; 55 | } 56 | 57 | #[link(name = "python2.7")] 58 | extern "C" { 59 | fn RPyFloat_Check(obj: *mut PyObjectRaw) -> c_long; 60 | fn RPyFloat_CheckExact(obj: *mut PyObjectRaw) -> c_long; 61 | fn RPyTuple_Check(obj: *mut PyObjectRaw) -> c_long; 62 | fn RPyList_Check(obj: *mut PyObjectRaw) -> c_long; 63 | fn RPyInt_Check(obj: *mut PyObjectRaw) -> c_long; 64 | fn RPyString_Check(obj: *mut PyObjectRaw) -> c_long; 65 | fn RPyIter_Check(obj: *mut PyObjectRaw) -> c_long; 66 | } 67 | 68 | /// Trait to allow interaction with the python interpreter. 69 | #[allow(bad_style)] 70 | pub trait PythonCAPI { 71 | unsafe fn Py_Initialize(&self) { 72 | Py_Initialize(); 73 | } 74 | unsafe fn Py_Finalize(&self) { 75 | Py_Finalize(); 76 | } 77 | unsafe fn PyImport_ImportModule(&self, name: *const c_char) -> *mut PyObjectRaw { 78 | PyImport_ImportModule(name) 79 | } 80 | unsafe fn PyInt_FromLong(&self, ival: c_long) -> *mut PyObjectRaw { 81 | PyInt_FromLong(ival) 82 | } 83 | unsafe fn PyInt_AsLong(&self, obj: *mut PyObjectRaw) -> c_long { 84 | PyInt_AsLong(obj) 85 | } 86 | unsafe fn PyFloat_FromDouble(&self, value: c_double) -> *mut PyObjectRaw { 87 | PyFloat_FromDouble(value) 88 | } 89 | unsafe fn PyFloat_AsDouble(&self, obj: *mut PyObjectRaw) -> c_double { 90 | PyFloat_AsDouble(obj) 91 | } 92 | unsafe fn PyTuple_New(&self, size: size_t) -> *mut PyObjectRaw { 93 | PyTuple_New(size) 94 | } 95 | unsafe fn PyTuple_GetItem(&self, tuple: *mut PyObjectRaw, pos: size_t) -> *mut PyObjectRaw { 96 | PyTuple_GetItem(tuple, pos) 97 | } 98 | unsafe fn PyTuple_SetItem(&self, tuple: *mut PyObjectRaw, pos: size_t, o: *mut PyObjectRaw) { 99 | PyTuple_SetItem(tuple, pos, o) 100 | } 101 | unsafe fn PyTuple_Size(&self, tuple: *mut PyObjectRaw) -> c_long { 102 | PyTuple_Size(tuple) 103 | } 104 | unsafe fn PyList_New(&self, size: size_t) -> *mut PyObjectRaw { 105 | PyList_New(size) 106 | } 107 | unsafe fn PyList_GetItem(&self, list: *mut PyObjectRaw, index: size_t) -> *mut PyObjectRaw { 108 | PyList_GetItem(list, index) 109 | } 110 | unsafe fn PyList_SetItem(&self, 111 | list: *mut PyObjectRaw, 112 | index: size_t, 113 | item: *mut PyObjectRaw) { 114 | PyList_SetItem(list, index, item) 115 | } 116 | unsafe fn PyList_Size(&self, list: *mut PyObjectRaw) -> c_long { 117 | PyList_Size(list) 118 | } 119 | unsafe fn Py_IncRef(&self, obj: *mut PyObjectRaw) { 120 | Py_IncRef(obj) 121 | } 122 | unsafe fn Py_DecRef(&self, obj: *mut PyObjectRaw) { 123 | Py_DecRef(obj) 124 | } 125 | unsafe fn PyFloat_Check(&self, obj: *mut PyObjectRaw) -> c_long { 126 | RPyFloat_Check(obj) 127 | } 128 | unsafe fn PyFloat_CheckExact(&self, obj: *mut PyObjectRaw) -> c_long { 129 | RPyFloat_CheckExact(obj) 130 | } 131 | unsafe fn PyTuple_Check(&self, obj: *mut PyObjectRaw) -> c_long { 132 | RPyTuple_Check(obj) 133 | } 134 | unsafe fn PyList_Check(&self, obj: *mut PyObjectRaw) -> c_long { 135 | RPyList_Check(obj) 136 | } 137 | unsafe fn PyInt_Check(&self, obj: *mut PyObjectRaw) -> c_long { 138 | RPyInt_Check(obj) 139 | } 140 | unsafe fn PyString_Check(&self, obj: *mut PyObjectRaw) -> c_long { 141 | RPyString_Check(obj) 142 | } 143 | unsafe fn PyIter_Check(&self, obj: *mut PyObjectRaw) -> c_long { 144 | RPyIter_Check(obj) 145 | } 146 | unsafe fn PyString_FromString(&self, string: *const c_char) -> *mut PyObjectRaw { 147 | PyString_FromString(string) 148 | } 149 | unsafe fn PyString_AsString(&self, obj: *mut PyObjectRaw) -> *const c_char { 150 | PyString_AsString(obj) 151 | } 152 | unsafe fn PyObject_GetAttrString(&self, 153 | object: *mut PyObjectRaw, 154 | attr: *const c_char) 155 | -> *mut PyObjectRaw { 156 | PyObject_GetAttrString(object, attr) 157 | } 158 | unsafe fn PyErr_Fetch(&self, 159 | ptype: *mut *mut PyObjectRaw, 160 | pvalue: *mut *mut PyObjectRaw, 161 | ptraceback: *mut *mut PyObjectRaw) { 162 | PyErr_Fetch(ptype, pvalue, ptraceback); 163 | } 164 | unsafe fn PyErr_NormalizeException(&self, 165 | ptype: *mut *mut PyObjectRaw, 166 | pvalue: *mut *mut PyObjectRaw, 167 | ptraceback: *mut *mut PyObjectRaw) { 168 | PyErr_NormalizeException(ptype, pvalue, ptraceback); 169 | } 170 | unsafe fn PyObject_Str(&self, obj: *mut PyObjectRaw) -> *mut PyObjectRaw { 171 | PyObject_Str(obj) 172 | } 173 | unsafe fn PyObject_CallObject(&self, 174 | callable_object: *mut PyObjectRaw, 175 | args: *mut PyObjectRaw) 176 | -> *mut PyObjectRaw { 177 | PyObject_CallObject(callable_object, args) 178 | } 179 | unsafe fn PyObject_GetIter(&self, obj: *mut PyObjectRaw) -> *mut PyObjectRaw { 180 | PyObject_GetIter(obj) 181 | } 182 | unsafe fn PyIter_Next(&self, obj: *mut PyObjectRaw) -> *mut PyObjectRaw { 183 | PyIter_Next(obj) 184 | } 185 | } 186 | 187 | impl PythonCAPI for PyState {} 188 | -------------------------------------------------------------------------------- /src/macroexpand.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int RPyFloat_Check(PyObject* obj) { 4 | return PyFloat_Check(obj); 5 | } 6 | 7 | int RPyFloat_CheckExact(PyObject* obj) { 8 | return PyFloat_CheckExact(obj); 9 | } 10 | 11 | int RPyTuple_Check(PyObject* obj) { 12 | return PyTuple_Check(obj); 13 | } 14 | 15 | int RPyList_Check(PyObject* obj) { 16 | return PyList_Check(obj); 17 | } 18 | 19 | int RPyInt_Check(PyObject* obj) { 20 | return PyInt_Check(obj); 21 | } 22 | 23 | int RPyString_Check(PyObject* obj) { 24 | return PyString_Check(obj); 25 | } 26 | 27 | int RPyIter_Check(PyObject* obj) { 28 | return PyIter_Check(obj); 29 | } 30 | -------------------------------------------------------------------------------- /src/primtypes.rs: -------------------------------------------------------------------------------- 1 | use libc::{c_long, size_t}; 2 | use std::ffi::{CStr, CString}; 3 | pub use base::{PyObject, ToPyType, FromPyType, PyState, PyIterator}; 4 | pub use ffi::{PythonCAPI, PyObjectRaw}; 5 | pub use base::PyError; 6 | 7 | macro_rules! prim_pytype ( 8 | ($base_type:ty, $cast_type:ty, $to:ident, $back:ident, $check:ident) => ( 9 | impl ToPyType for $base_type { 10 | fn to_py_object<'a>(&self, state : &'a PyState) -> Result, PyError> { 11 | unsafe { 12 | let raw = state.$to(*self as $cast_type); 13 | if !raw.is_null() && state.$check(raw) > 0 { 14 | Ok(PyObject::new(state, raw)) 15 | } else { 16 | Err(PyError::ToTypeConversionError) 17 | } 18 | } 19 | } 20 | } 21 | 22 | impl FromPyType for $base_type { 23 | fn from_py_object(state : &PyState, py_object : PyObject) -> Result<$base_type, PyError> { 24 | unsafe { 25 | if !py_object.raw.is_null() && state.$check(py_object.raw) > 0 { 26 | Ok(state.$back(py_object.raw) as $base_type) 27 | } else { 28 | Err(PyError::FromTypeConversionError) 29 | } 30 | } 31 | } 32 | } 33 | ) 34 | ); 35 | 36 | prim_pytype!(f64, f64, PyFloat_FromDouble, PyFloat_AsDouble, PyFloat_Check); 37 | prim_pytype!(f32, f64, PyFloat_FromDouble, PyFloat_AsDouble, PyFloat_Check); 38 | prim_pytype!(i64, c_long, PyInt_FromLong, PyInt_AsLong, PyInt_Check); 39 | prim_pytype!(i32, c_long, PyInt_FromLong, PyInt_AsLong, PyInt_Check); 40 | prim_pytype!(isize, c_long, PyInt_FromLong, PyInt_AsLong, PyInt_Check); 41 | prim_pytype!(usize, c_long, PyInt_FromLong, PyInt_AsLong, PyInt_Check); 42 | prim_pytype!(u8, c_long, PyInt_FromLong, PyInt_AsLong, PyInt_Check); 43 | prim_pytype!(u32, c_long, PyInt_FromLong, PyInt_AsLong, PyInt_Check); 44 | prim_pytype!(u64, c_long, PyInt_FromLong, PyInt_AsLong, PyInt_Check); 45 | 46 | macro_rules! expr { ($x:expr) => ($x) } // HACK 47 | 48 | macro_rules! tuple_pytype ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+} => ( 49 | impl<$($T:ToPyType),+> ToPyType for ($($T,)+) { 50 | fn to_py_object<'a>(&self, state : &'a PyState) -> Result, PyError> { 51 | unsafe { 52 | let raw = state.PyTuple_New($length); 53 | $(let $refN = try!(expr!(self.$n).to_py_object(state));)+ 54 | $(state.Py_IncRef($refN.raw);)+ 55 | $(state.PyTuple_SetItem(raw, expr!($n), $refN.raw);)+ 56 | 57 | if !raw.is_null() { 58 | Ok(PyObject::new(state, raw)) 59 | } else { 60 | Err(PyError::ToTypeConversionError) 61 | } 62 | } 63 | } 64 | } 65 | 66 | impl<$($T:FromPyType),+> FromPyType for ($($T,)+) { 67 | fn from_py_object(state : &PyState, py_object : PyObject) -> Result<($($T,)+), PyError> { 68 | unsafe { 69 | if py_object.raw.is_null() && state.PyTuple_Check(py_object.raw) > 0 { 70 | Err(PyError::FromTypeConversionError) 71 | } else { 72 | let raw = py_object.raw; 73 | if state.PyTuple_Size(raw) == $length { 74 | $(let $refN = state.PyTuple_GetItem(raw, expr!($n));)+ 75 | //TODO is there a better way to do this check? 76 | let no_null = vec!($(!$refN.is_null(), ) +).iter().all(|&x| x); 77 | if no_null { 78 | $(let $refN = PyObject::new(state, $refN);)+ 79 | $(let $refN = try!(state.from_py_object::<$T>($refN));)+ 80 | Ok(($($refN,)+)) 81 | } else { 82 | Err(PyError::ToTypeConversionError) 83 | } 84 | } else { 85 | Err(PyError::ToTypeConversionError) 86 | } 87 | } 88 | } 89 | } 90 | } 91 | )); 92 | 93 | impl ToPyType for Vec { 94 | fn to_py_object<'a>(&'a self, state: &'a PyState) -> Result, PyError> { 95 | unsafe { 96 | let raw = state.PyList_New(self.len() as size_t); 97 | for (i, item) in self.iter().enumerate() { 98 | let pyitem = try!(item.to_py_object(state)); 99 | state.Py_IncRef(pyitem.raw); 100 | state.PyList_SetItem(raw, i as size_t, pyitem.raw); 101 | } 102 | if !raw.is_null() { 103 | Ok(PyObject::new(state, raw)) 104 | } else { 105 | Err(PyError::ToTypeConversionError) 106 | } 107 | } 108 | } 109 | } 110 | 111 | impl FromPyType for Vec { 112 | fn from_py_object(state: &PyState, py_object: PyObject) -> Result, PyError> { 113 | unsafe { 114 | if !py_object.raw.is_null() && state.PyList_Check(py_object.raw) > 0 { 115 | let raw = py_object.raw; 116 | let size = state.PyList_Size(raw) as usize; 117 | let mut v = Vec::with_capacity(size); 118 | for i in 0..size { 119 | let rawitem = state.PyList_GetItem(raw, i as size_t); 120 | if rawitem.is_null() { 121 | return Err(PyError::FromTypeConversionError); 122 | } 123 | let pyitem = PyObject::new(state, rawitem); 124 | let item = try!(state.from_py_object::(pyitem)); 125 | v.push(item); 126 | } 127 | Ok(v) 128 | } else { 129 | Err(PyError::FromTypeConversionError) 130 | } 131 | } 132 | } 133 | } 134 | 135 | tuple_pytype!(1,(ref0, 0, A)); 136 | tuple_pytype!(2,(ref0, 0, A),(ref1, 1, B)); 137 | tuple_pytype!(3, (ref0, 0, A), (ref1, 1, B), (ref2, 2, C)); 138 | tuple_pytype!(4, (ref0, 0, A), (ref1, 1, B), (ref2, 2, C), (ref3, 3, D)); 139 | tuple_pytype!(5, (ref0, 0, A), (ref1, 1, B), (ref2, 2, C), (ref3, 3, D), 140 | (ref4, 4, E)); 141 | tuple_pytype!(6, (ref0, 0, A), (ref1, 1, B), (ref2, 2, C), (ref3, 3, D), 142 | (ref4, 4, E), (ref5, 5, F)); 143 | tuple_pytype!(7, (ref0, 0, A), (ref1, 1, B), (ref2, 2, C), (ref3, 3, D), 144 | (ref4, 4, E), (ref5, 5, F), (ref6, 6, G)); 145 | tuple_pytype!(8, (ref0, 0, A), (ref1, 1, B), (ref2, 2, C), (ref3, 3, D), 146 | (ref4, 4, E), (ref5, 5, F), (ref6, 6, G),(ref7, 7, H)); 147 | tuple_pytype!(9, (ref0, 0, A), (ref1, 1, B), (ref2, 2, C), (ref3, 3, D), 148 | (ref4, 4, E), (ref5, 5, F),(ref6, 6, G),(ref7, 7, H),(ref8, 8, I)); 149 | 150 | impl ToPyType for String { 151 | fn to_py_object<'a, 'b>(&'b self, state: &'a PyState) -> Result, PyError> { 152 | // FIXME code duplicated from str slice 153 | unsafe { 154 | let raw = state.PyString_FromString(CString::new(self.as_bytes()).unwrap().as_ptr()); 155 | if !raw.is_null() && state.PyString_Check(raw) > 0 { 156 | Ok(PyObject::new(state, raw)) 157 | } else { 158 | Err(PyError::ToTypeConversionError) 159 | } 160 | } 161 | } 162 | } 163 | 164 | impl FromPyType for String { 165 | fn from_py_object(state: &PyState, py_object: PyObject) -> Result { 166 | unsafe { 167 | if !py_object.raw.is_null() && state.PyString_Check(py_object.raw) > 0 { 168 | let c_str = state.PyString_AsString(py_object.raw); 169 | let string = String::from_utf8(CStr::from_ptr(c_str).to_bytes().to_vec()).unwrap(); 170 | Ok(string) 171 | } else { 172 | Err(PyError::FromTypeConversionError) 173 | } 174 | } 175 | } 176 | } 177 | 178 | impl ToPyType for str { 179 | fn to_py_object<'a>(&self, state: &'a PyState) -> Result, PyError> { 180 | unsafe { 181 | let raw = state.PyString_FromString(CString::new(self).unwrap().as_ptr()); 182 | if !raw.is_null() && state.PyString_Check(raw) > 0 { 183 | Ok(PyObject::new(state, raw)) 184 | } else { 185 | Err(PyError::ToTypeConversionError) 186 | } 187 | } 188 | } 189 | } 190 | 191 | /// Structure that represents an empty tuple in python 192 | pub struct NoArgs; 193 | 194 | impl ToPyType for NoArgs { 195 | fn to_py_object<'a>(&self, state: &'a PyState) -> Result, PyError> { 196 | Ok(PyObject::empty_tuple(state)) 197 | } 198 | } 199 | 200 | impl FromPyType for NoArgs { 201 | fn from_py_object(_: &PyState, _: PyObject) -> Result { 202 | Ok(NoArgs) 203 | } 204 | } 205 | 206 | #[cfg(test)] 207 | mod test { 208 | use base::PyState; 209 | use super::{ToPyType, FromPyType, NoArgs}; 210 | macro_rules! try_or_panic ( 211 | ($e:expr) => (match $e { Ok(e) => e, Err(e) => panic!("{:?}", e) }) 212 | ); 213 | 214 | macro_rules! num_to_py_object_and_back ( 215 | ($t:ty, $func_name:ident) => ( 216 | #[test] 217 | fn $func_name() { 218 | let py = PyState::new(); 219 | let value = 123 as $t; 220 | let py_object = try_or_panic!(value.to_py_object(&py)); 221 | let returned = try_or_panic!(py.from_py_object::<$t>(py_object)); 222 | assert_eq!(returned, 123 as $t); 223 | } 224 | ) 225 | ); 226 | 227 | num_to_py_object_and_back!(f64, to_from_f64); 228 | num_to_py_object_and_back!(f32, to_from_f32); 229 | num_to_py_object_and_back!(i64, to_from_i64); 230 | num_to_py_object_and_back!(i32, to_from_i32); 231 | num_to_py_object_and_back!(isize, to_from_isize); 232 | num_to_py_object_and_back!(usize, to_from_usize); 233 | num_to_py_object_and_back!(u8, to_from_u8); 234 | num_to_py_object_and_back!(u32, to_from_32); 235 | num_to_py_object_and_back!(u64, to_from_54); 236 | 237 | macro_rules! tuple_to_py_object_and_back (($val:expr, $T:ty, $func_name:ident) => ( 238 | #[test] 239 | fn $func_name() { 240 | let py = PyState::new(); 241 | let v = $val; 242 | let py_object = try_or_panic!(v.to_py_object(&py)); 243 | let returned = try_or_panic!(py.from_py_object::<$T>(py_object)); 244 | assert_eq!(returned, $val); 245 | } 246 | )); 247 | 248 | tuple_to_py_object_and_back!((1,), (isize,), to_and_from_tuple1); 249 | tuple_to_py_object_and_back!((1,2), (isize,isize), to_and_from_tuple2); 250 | tuple_to_py_object_and_back!((1,2,3), (isize,isize,isize), to_and_from_tuple3); 251 | tuple_to_py_object_and_back!((1,2,3,4), (isize,isize,isize,isize), to_and_from_tuple4); 252 | tuple_to_py_object_and_back!((1,2,3,4,5), (isize,isize,isize,isize,isize), to_and_from_tuple5); 253 | tuple_to_py_object_and_back!((1,2,3,4,5,6), (isize,isize,isize,isize,isize,isize), to_and_from_tuple6); 254 | 255 | #[test] 256 | fn to_and_from_list() { 257 | let val = vec![1, 2, 3]; 258 | let py = PyState::new(); 259 | let py_object = try_or_panic!(val.to_py_object(&py)); 260 | let returned = try_or_panic!(py.from_py_object::>(py_object)); 261 | assert_eq!(returned, val); 262 | } 263 | 264 | #[test] 265 | fn mixed_convert() { 266 | let py = PyState::new(); 267 | let value = 123f32; 268 | let py_object = try_or_panic!(value.to_py_object(&py)); 269 | let result = py.from_py_object::(py_object); 270 | match result { 271 | Err(_) => (), 272 | Ok(x) => panic!("should have failed but got {:?}", x), 273 | }; 274 | } 275 | 276 | #[test] 277 | fn float_to_tuple_should_err() { 278 | let py = PyState::new(); 279 | let value = 123f32; 280 | let py_object = try_or_panic!(value.to_py_object(&py)); 281 | let result = py.from_py_object::<(isize, isize)>(py_object); 282 | match result { 283 | Err(_) => (), 284 | Ok(x) => panic!("should have failed but got {:?}", x), 285 | }; 286 | } 287 | 288 | #[test] 289 | fn tuple_to_float_should_err() { 290 | let py = PyState::new(); 291 | let value = (123f32, 234f32, 1f32, 3f32); 292 | let py_object = try_or_panic!(value.to_py_object(&py)); 293 | let result = py.from_py_object::(py_object); 294 | match result { 295 | Err(_) => (), 296 | Ok(x) => panic!("should have failed but got {:?}", x), 297 | }; 298 | } 299 | 300 | #[test] 301 | fn string_to_py_object_and_back() { 302 | let py = PyState::new(); 303 | let value = "Hello world".to_string(); 304 | let py_object = try_or_panic!(value.to_py_object(&py)); 305 | let result = try_or_panic!(py.from_py_object::(py_object)); 306 | assert_eq!(&result, "Hello world"); 307 | } 308 | 309 | #[test] 310 | fn ref_string_to_py_object_and_back_to_string() { 311 | let py = PyState::new(); 312 | let value = "Hello world"; 313 | let py_object = try_or_panic!(value.to_py_object(&py)); 314 | let result = try_or_panic!(py.from_py_object::(py_object)); 315 | assert_eq!(&result, "Hello world"); 316 | } 317 | 318 | #[test] 319 | fn no_args() { 320 | // Just Don't fail to convert. Assuming its correct 321 | let py = PyState::new(); 322 | let value = NoArgs; 323 | let py_object = try_or_panic!(value.to_py_object(&py)); 324 | let _ = try_or_panic!(py.from_py_object::(py_object)); 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /src/rustpy.rs: -------------------------------------------------------------------------------- 1 | //! A simple library to allow for easy use of python from rust. 2 | //! 3 | //! This library is meant to be middle ware for users wanting to use 4 | //! python libraries from rust. It allows users who want to quickly use exciting 5 | //! tools, at the price of speed, and to get going fast. 6 | //! Originally it was intended to bootstrap machine learning for rust. 7 | //! 8 | //! It provides a way to interact 9 | //! with a python interpreter, via [`PyState`](struct.PyState.html) as well as quick conversion 10 | //! from rust types to python types via the [`ToPyType`](trait.ToPyType.html) and 11 | //! [`FromPyType`](trait.FromPyType.html) traits. 12 | //! 13 | //! 14 | //! ```rust 15 | //! extern crate rustpy; 16 | //! use rustpy::{ToPyType, FromPyType, PyState}; 17 | //! 18 | //! 19 | //! fn main() { 20 | //! let py = PyState::new(); 21 | //! let module = py.get_module("math").unwrap(); 22 | //! let func = module.get_func("sqrt").unwrap(); 23 | //! let v = (144f32, ); 24 | //! let args = v.to_py_object(&py).unwrap(); 25 | //! let untyped_res = func.call(&args).unwrap(); 26 | //! let result = py.from_py_object::(untyped_res).unwrap(); 27 | //! assert_eq!(result, 12f32); 28 | //! } 29 | //! ``` 30 | //! 31 | #![crate_type = "lib"] 32 | 33 | extern crate libc; 34 | #[macro_use] 35 | extern crate lazy_static; 36 | 37 | pub use base::{ToPyType, FromPyType, PyState, PyObject, PyObjectRaw, PyError, PyIterator}; 38 | pub use primtypes::NoArgs; 39 | 40 | mod base; 41 | mod primtypes; 42 | mod ffi; 43 | --------------------------------------------------------------------------------