├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── ci └── travis_setup.sh ├── release.md ├── setup.py ├── src ├── librustypy │ ├── Cargo.toml │ ├── README.md │ ├── __init__.py │ ├── lib.rs │ ├── macros.rs │ └── pytypes │ │ ├── mod.rs │ │ ├── pybool.rs │ │ ├── pydict.rs │ │ ├── pylist.rs │ │ ├── pystring.rs │ │ └── pytuple.rs └── rustypy │ ├── __init__.py │ ├── __main__.py │ ├── pywrapper.py │ ├── rswrapper │ ├── __init__.py │ ├── ffi_defs.py │ ├── pytypes.py │ └── rswrapper.py │ ├── scripts.py │ └── type_checkers.py ├── tests ├── py_test_lib │ ├── Cargo.toml │ ├── lib.rs │ ├── nested_types.rs │ ├── primitives.rs │ ├── submodules.rs │ └── test_package │ │ ├── __init__.py │ │ ├── basics │ │ ├── __init__.py │ │ ├── nested_types.py │ │ └── primitives.py │ │ ├── firstdir │ │ ├── call_from_first.py │ │ └── subfirstdir │ │ │ └── call_from_subfirst.py │ │ ├── mod.rs │ │ ├── root_module_1.py │ │ └── root_module_2.py ├── rs_test_lib │ ├── Cargo.toml │ ├── lib.rs │ └── nested.rs ├── test_py_to_rs.py └── test_rs_to_py.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | # Python generated 2 | .tox 3 | *.egg-info 4 | .eggs 5 | __pycache__ 6 | build 7 | dist 8 | *.so 9 | 10 | # Rust generated 11 | Cargo.lock 12 | target 13 | **/librustypy.so 14 | **/rustypy_pybind* 15 | 16 | # IDESs 17 | .vscode 18 | .idea 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | language: python 4 | python: 5 | - "3.5" 6 | - "3.6" 7 | - "3.7" 8 | - "3.8" 9 | 10 | env: 11 | global: 12 | - TRAVIS_RUST_VERSION=stable 13 | - RUST_BACKTRACE=1 14 | - CI_HOME=`pwd` 15 | 16 | before_install: 17 | - source ./ci/travis_setup.sh 18 | 19 | install: 20 | - rustc -V 21 | - pip install . 22 | 23 | script: 24 | - python ./tests/test_py_to_rs.py 25 | - python ./tests/test_rs_to_py.py 26 | - cargo clippy --manifest-path ./src/librustypy/Cargo.toml 27 | - cargo test --manifest-path ./src/librustypy/Cargo.toml 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Ignacio Duart Gómez 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of rustypy nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Archived 2 | I recommend using PyO3 as it's a better maintained and feature complete project. 3 | 4 | # RustyPy [![Build Status](https://travis-ci.org/iduartgomez/rustypy.svg?branch=master)](https://travis-ci.org/iduartgomez/rustypy) 5 | RustyPy is a code generator for generating binding functions between Rust and 6 | Python files. In addition it provides a series of types for interfacing from 7 | Python with Rust, with automatic type conversion handling for primitives and 8 | other basic types (like vectors, hash maps and tuples) and an API for working 9 | with those (both from Rust and Python). 10 | 11 | ## Features 12 | - Generate bindings in Rust targetting Python functions. 13 | - Generate bindings in Python targetting Rust functions. 14 | - Provides types for interfacing between Rust and Python. 15 | 16 | ## Installation 17 | To install RustyPy just use pip: 18 | ``` 19 | pip install rustypy 20 | ``` 21 | RustyPy requires Python 3.5 or more and works with Rust stable. 22 | 23 | To target Python from Rust the package [cpython](https://github.com/dgrunwald/rust-cpython) 24 | is required to initialize the package. 25 | 26 | ## Documentation 27 | * [Python in Rust](https://github.com/iduartgomez/rustypy/wiki/Python-in-Rust) 28 | * [Rust in Python](https://github.com/iduartgomez/rustypy/wiki/Rust-in-Python) 29 | * [Type conversions](https://github.com/iduartgomez/rustypy/wiki/Type-conversions) 30 | 31 | ## Usage 32 | RustyPy includes a command line interface to generate the code, which you can 33 | embeed in your build chain if it's necessary. 34 | 35 | ### Generate Python bindings in Rust 36 | You can execute the script writing: 37 | ``` 38 | $rustypy -h 39 | ``` 40 | or 41 | ``` 42 | $python -m rustypy -h 43 | ``` 44 | make sure that rustypy is in your current Python path. The help command has 45 | all the information to generate bindings succesfully. 46 | 47 | It also includes functions to generate bindings dynamically. In Python use: 48 | ``` 49 | from rustypy.pywrapper import bind_py_pckg_funcs 50 | # default prefix is "rust_bind_" 51 | optional = ["my_bind_prefix_", "other_prefix_"] 52 | bind_py_pckg_funcs(prefixes=optional) 53 | ``` 54 | This function will generate the bindings for the package from which is 55 | called from (so the package must be initiated placing an \__init__.py file in 56 | one of the parents folders). 57 | 58 | More info: [Python in Rust](https://github.com/iduartgomez/rustypy/wiki/Python-in-Rust) 59 | 60 | ### Generate Rust bindings in Python 61 | Due to the nature of Python this is done dynamically, so no files 62 | are generated and the bindings are wrapped appropriately with their own callables 63 | from Python. 64 | 65 | ``` 66 | from rustypy.rswrapper import bind_rs_crate_funcs 67 | 68 | source_path = "/home/user/workspace/rs_test_lib" 69 | compiled_lib = "/home/user/workspace/rs_test_lib/target/debug/libtest_lib.so" 70 | 71 | # default prefix is "python_bind_" 72 | optional = ["my_bind_prefix_", "other_prefix_"] 73 | lib_binds = bind_rs_crate_funcs(source_path, compiled_lib, prefixes=optional) 74 | 75 | lib_binds.my_bind_prefix__ffi_function("Hello from Python!") 76 | ``` 77 | 78 | There is no concept of 'module' in C (which is the language used for interfacing) 79 | so the functions cannot be namedspaced as you would in pure Rust. Read about 80 | Rust FFI in [the book](https://doc.rust-lang.org/stable/book/ffi.html). 81 | 82 | More info: [Rust in Python](https://github.com/iduartgomez/rustypy/wiki/Rust-in-Python). 83 | 84 | Rust crate [documentation](https://iduartgomez.github.io/rustypy/). 85 | -------------------------------------------------------------------------------- /ci/travis_setup.sh: -------------------------------------------------------------------------------- 1 | ### Setup Rust toolchain ### 2 | 3 | curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain=$TRAVIS_RUST_VERSION 4 | export PATH="$PATH:$HOME/.cargo/bin" 5 | 6 | rustup component add clippy 7 | rustup component add rustfmt 8 | 9 | ### Setup linking paths ### 10 | PYTHON_LIB=$(python -c "import sysconfig; print(sysconfig.get_config_var('LIBDIR'))") 11 | export PYTHON_LIB 12 | export LIBRARY_PATH="$LIBRARY_PATH:$PYTHON_LIB" 13 | export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$PYTHON_LIB:$HOME/rust/lib" 14 | -------------------------------------------------------------------------------- /release.md: -------------------------------------------------------------------------------- 1 | # Release procedure 2 | 3 | 1. Ensure pypandoc & pandoc are installed: 4 | > $ apt-get install -y pandoc \ 5 | $ pip install "pypandoc==1.4" 6 | 2. Ensure version numbers are aligned ( 7 | `./src/librustypy/Cargo.toml`, 8 | `.src/rustypy/__init__.py`, 9 | `setup.py` 10 | ) 11 | 3. Build python package: 12 | > $ python setup.py sdist bdist wheel 13 | 4. Publish: 14 | > $ cargo publish \ 15 | > $ twine -u *** -p *** upload dist/* 16 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import sys 4 | from io import StringIO 5 | 6 | from setuptools import find_packages, setup 7 | 8 | if sys.version_info[0:2] < (3, 5): 9 | raise RuntimeError("Python version >= 3.5 required.") 10 | 11 | path = os.path.abspath(os.path.dirname(__file__)) 12 | 13 | 14 | def get_rustypy_version(): 15 | # import importlib 16 | # sys.path.append(os.path.join(path, 'src')) 17 | # mod = importlib.import_module('rustypy') 18 | 19 | # FIXME: get library version dinamically 20 | return "0.1.17" 21 | 22 | 23 | rustypy_version = get_rustypy_version() 24 | 25 | 26 | def generate_description(): 27 | # get long description 28 | f = os.path.join(path, 'README.md') 29 | try: 30 | import pypandoc 31 | if pypandoc.__version__ == "1.4": 32 | long_description = '\n' + pypandoc.convert_file(f, 'rst') 33 | else: 34 | raise ImportError 35 | except ImportError: 36 | import logging 37 | logging.warning( 38 | "warning: pypandoc not found or incompatible, could not convert " + 39 | "Markdown to RST") 40 | long_description = '\n' + open(f, 'r').read() 41 | return long_description 42 | 43 | 44 | def update_crate_version(): 45 | import re 46 | new_file = StringIO() 47 | rslib = os.path.join(path, 'src', 'rslib') 48 | old_ver = re.compile(r'^version = "(.*)"') 49 | ori_toml = os.path.join(rslib, 'Cargo.toml') 50 | new_toml = os.path.join(rslib, 'Cargo.temp.toml') 51 | with open(ori_toml, 'r') as f: 52 | for l in f: 53 | ver = re.match(old_ver, l) 54 | if ver: 55 | version = 'version = "{}"\n'.format(rustypy_version) 56 | new_file.write(version) 57 | else: 58 | new_file.write(l) 59 | f = open(new_toml, 'w') 60 | f.write(new_file.getvalue()) 61 | f.close() 62 | os.remove(ori_toml) 63 | os.rename(new_toml, ori_toml) 64 | 65 | 66 | def build_extension(): 67 | try: 68 | from setuptools_rust import RustExtension, Binding 69 | except ImportError: 70 | import subprocess 71 | errno = subprocess.call( 72 | [sys.executable, "-m", "pip", "install", "setuptools-rust"]) 73 | if errno: 74 | print("Please install setuptools-rust package") 75 | raise SystemExit(errno) 76 | else: 77 | from setuptools_rust import RustExtension, Binding 78 | 79 | import pathlib 80 | lib_path = pathlib.Path(os.path.abspath(__file__)).parent.joinpath( 81 | 'src', 'librustypy', 'Cargo.toml') 82 | return [RustExtension("librustypy", path=str(lib_path), binding=Binding.NoBinding)] 83 | 84 | 85 | setup( 86 | name="rustypy", 87 | version=rustypy_version, 88 | description='Automatic FFI generation for Python <-> Rust interfacing.', 89 | long_description=generate_description(), 90 | url='https://github.com/iduartgomez/rustypy', 91 | author='Ignacio Duart Gómez', 92 | author_email='iduartgomez@gmail.com', 93 | license='BSD 3-Clause', 94 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 95 | classifiers=[ 96 | 'Intended Audience :: Developers', 97 | 'License :: OSI Approved :: MIT License', 98 | 'Operating System :: POSIX', 99 | 'Programming Language :: Python :: 3.5', 100 | 'Programming Language :: Python :: 3.6', 101 | 'Programming Language :: Python :: 3.7', 102 | 'Programming Language :: Rust', 103 | 'Topic :: Software Development :: Code Generators' 104 | ], 105 | keywords='rust autogenerated FFI', 106 | rust_extensions=build_extension(), 107 | packages=find_packages('src'), 108 | package_dir={'': 'src'}, 109 | package_data={'librustypy': ['Cargo.toml', '**/*.rs', '*.rs']}, 110 | setup_requires=[ 111 | 'setuptools_rust', 112 | 'wheel' 113 | ], 114 | # install_requires=['cffi'], 115 | entry_points={ 116 | 'console_scripts': [ 117 | 'rustypy=rustypy.scripts:cli', 118 | ], 119 | }, 120 | ) 121 | -------------------------------------------------------------------------------- /src/librustypy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustypy" 3 | version = "0.1.17" 4 | authors = ["IDG "] 5 | edition = "2018" 6 | license = "BSD-3-Clause" 7 | description = "Python from/to Rust bindings" 8 | documentation = "https://github.com/iduartgomez/rustypy/wiki" 9 | repository = "https://github.com/iduartgomez/rustypy" 10 | 11 | [dependencies] 12 | cpython = { version="~0.3.0", features=["python3-sys"] } 13 | libc = "~0.2.62" 14 | syn = { version="~1.0.17", features=["full", "visit"] } 15 | walkdir = "1" 16 | 17 | [lib] 18 | name = "rustypy" 19 | path = "./lib.rs" 20 | # dylib for debugging 21 | crate-type = ["lib", "cdylib"] 22 | -------------------------------------------------------------------------------- /src/librustypy/README.md: -------------------------------------------------------------------------------- 1 | # RustyPy [![Build Status](https://travis-ci.org/iduartgomez/rustypy.svg?branch=master)](https://travis-ci.org/iduartgomez/rustypy) 2 | 3 | Companion library to generate Rust/Python bindings from Python/Rust. 4 | 5 | Visit the [pip](https://pypi.org/project/rustypy/) repo or [GitHub](https://github.com/iduartgomez/rustypy) for more info. -------------------------------------------------------------------------------- /src/librustypy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iduartgomez/rustypy/b6d0f5ba7e4961bed75f454a21dd3ce25243faee/src/librustypy/__init__.py -------------------------------------------------------------------------------- /src/librustypy/lib.rs: -------------------------------------------------------------------------------- 1 | //! Binding Rust with Python, both ways! 2 | //! 3 | //! This library will generate and handle type conversions between Python and 4 | //! Rust. To use Python from Rust refer to the 5 | //! [library wiki](https://github.com/iduartgomez/rustypy/wiki), more general examples 6 | //! and information on how to use Rust in Python can also be found there. 7 | //! 8 | //! Checkout the [PyTypes](../rustypy/pytypes/index.html) module documentation for more information 9 | //! on how to write foreign functions that are compliant with Python as well as using the custom 10 | //! types that will ease type conversion. 11 | #![crate_type = "cdylib"] 12 | 13 | extern crate cpython; 14 | extern crate libc; 15 | extern crate syn; 16 | extern crate walkdir; 17 | 18 | use std::fs::File; 19 | use std::io::Read; 20 | use std::path::Path; 21 | use std::ptr; 22 | 23 | use libc::size_t; 24 | 25 | mod macros; 26 | pub mod pytypes; 27 | 28 | // re-export 29 | pub use self::pytypes::pybool::PyBool; 30 | pub use self::pytypes::pydict::PyDict; 31 | pub use self::pytypes::pylist::PyList; 32 | pub use self::pytypes::pystring::PyString; 33 | pub use self::pytypes::pytuple::PyTuple; 34 | pub use self::pytypes::PyArg; 35 | 36 | #[doc(hidden)] 37 | #[no_mangle] 38 | pub unsafe extern "C" fn parse_src( 39 | path: *mut PyString, 40 | krate_data: &mut KrateData, 41 | ) -> *mut PyString { 42 | let path = PyString::from_ptr_to_string(path); 43 | let path: &Path = path.as_ref(); 44 | let dir = if let Some(parent) = path.parent() { 45 | parent 46 | } else { 47 | // unlikely this happens, but just in case 48 | return PyString::from("crate in root directory not allowed".to_string()).into_raw(); 49 | }; 50 | for entry in walkdir::WalkDir::new(dir) 51 | .into_iter() 52 | .filter_map(|e| e.ok()) 53 | .filter(|e| { 54 | if let Some(ext) = e.path().extension() { 55 | ext == "rs" 56 | } else { 57 | false 58 | } 59 | }) 60 | { 61 | if let Err(err) = parse_file(krate_data, entry.path()) { 62 | return err; 63 | } 64 | } 65 | ptr::null_mut::() 66 | } 67 | 68 | fn parse_file(krate_data: &mut KrateData, path: &Path) -> Result<(), *mut PyString> { 69 | let mut f = match File::open(path) { 70 | Ok(file) => file, 71 | Err(_) => { 72 | return Err( 73 | PyString::from(format!("path not found: {}", path.to_str().unwrap())).into_raw(), 74 | ) 75 | } 76 | }; 77 | let mut src = String::new(); 78 | if f.read_to_string(&mut src).is_err() { 79 | return Err(PyString::from(format!( 80 | "failed to read the source file: {}", 81 | path.to_str().unwrap() 82 | )) 83 | .into_raw()); 84 | } 85 | match syn::parse_file(&src) { 86 | Ok(krate) => { 87 | syn::visit::visit_file(krate_data, &krate); 88 | krate_data.collect_values(); 89 | if krate_data.collected.is_empty() { 90 | return Err(PyString::from("zero function calls parsed".to_string()).into_raw()); 91 | } 92 | } 93 | Err(err) => return Err(PyString::from(format!("{}", err)).into_raw()), 94 | }; 95 | Ok(()) 96 | } 97 | 98 | #[doc(hidden)] 99 | pub struct KrateData { 100 | functions: Vec, 101 | collected: Vec, 102 | prefixes: Vec, 103 | } 104 | 105 | impl KrateData { 106 | fn new(prefixes: Vec) -> KrateData { 107 | KrateData { 108 | functions: vec![], 109 | collected: vec![], 110 | prefixes, 111 | } 112 | } 113 | 114 | fn collect_values(&mut self) { 115 | let mut add = true; 116 | for v in self.functions.drain(..) { 117 | let FnDef { 118 | name: mut fndef, 119 | args, 120 | output, 121 | } = v; 122 | let original_name = fndef.clone(); 123 | if !args.is_empty() { 124 | fndef.push_str("::"); 125 | args.iter().fold(&mut fndef, |acc, arg| { 126 | if let Ok(repr) = type_repr(arg, None) { 127 | acc.push_str(&repr); 128 | acc.push(';'); 129 | } else { 130 | eprintln!( 131 | "could not generate bindings for fn `{}`; unacceptable parameters 132 | ", 133 | original_name 134 | ); 135 | add = false; 136 | } 137 | acc 138 | }); 139 | } else { 140 | // function w/o arguments 141 | fndef.push_str("::();"); 142 | } 143 | if add { 144 | match output { 145 | syn::ReturnType::Default => fndef.push_str("type(void)"), 146 | syn::ReturnType::Type(_, ty) => { 147 | if let Ok(ty) = type_repr(&ty, None) { 148 | fndef.push_str(&ty) 149 | } else { 150 | continue; 151 | } 152 | } 153 | } 154 | self.collected.push(fndef); 155 | } else { 156 | add = true 157 | } 158 | } 159 | } 160 | 161 | fn add_fn(&mut self, name: String, fn_decl: &syn::ItemFn) { 162 | for prefix in &self.prefixes { 163 | if name.starts_with(prefix) { 164 | let syn::ItemFn { sig, .. } = fn_decl.clone(); 165 | let mut args = vec![]; 166 | for arg in sig.inputs { 167 | match arg { 168 | syn::FnArg::Typed(pat_ty) => args.push(*pat_ty.ty), 169 | _ => continue, 170 | } 171 | } 172 | self.functions.push(FnDef { 173 | name, 174 | args, 175 | output: sig.output, 176 | }); 177 | break; 178 | } 179 | } 180 | } 181 | 182 | fn iter_krate(&self, idx: usize) -> Option<&str> { 183 | if self.collected.len() >= (idx + 1) { 184 | Some(&self.collected[idx]) 185 | } else { 186 | None 187 | } 188 | } 189 | } 190 | 191 | fn type_repr(ty: &syn::Type, r: Option<&str>) -> Result { 192 | let mut repr = String::new(); 193 | match ty { 194 | syn::Type::Path(path) => { 195 | let syn::TypePath { path, .. } = path; 196 | if let Some(ty) = path.segments.last() { 197 | if let Some(r) = r { 198 | Ok(format!("type({} {})", r, ty.ident)) 199 | } else { 200 | Ok(format!("type({})", ty.ident)) 201 | } 202 | } else { 203 | Err(()) 204 | } 205 | } 206 | syn::Type::Ptr(ty) => { 207 | let syn::TypePtr { 208 | elem, mutability, .. 209 | } = ty; 210 | let m = match mutability { 211 | Some(_) => "*mut", 212 | _ => "*const", 213 | }; 214 | repr.push_str(&type_repr(&*elem, Some(m))?); 215 | Ok(repr) 216 | } 217 | syn::Type::Reference(ty) => { 218 | let syn::TypeReference { 219 | elem, mutability, .. 220 | } = ty; 221 | let m = match mutability { 222 | Some(_) => "&mut", 223 | _ => "&", 224 | }; 225 | repr.push_str(&type_repr(&*elem, Some(m))?); 226 | Ok(repr) 227 | } 228 | _ => Err(()), 229 | } 230 | } 231 | 232 | impl<'ast> syn::visit::Visit<'ast> for KrateData { 233 | fn visit_item(&mut self, item: &syn::Item) { 234 | match item { 235 | syn::Item::Fn(fn_decl, ..) => { 236 | if let syn::Visibility::Public(_) = fn_decl.vis { 237 | let name = format!("{}", fn_decl.sig.ident); 238 | self.add_fn(name, &*fn_decl) 239 | } 240 | } 241 | syn::Item::Mod(mod_item) if mod_item.content.is_some() => { 242 | for item in &mod_item.content.as_ref().unwrap().1 { 243 | self.visit_item(item); 244 | } 245 | } 246 | _ => {} 247 | } 248 | } 249 | } 250 | 251 | struct FnDef { 252 | name: String, 253 | output: syn::ReturnType, 254 | args: Vec, 255 | } 256 | 257 | // C FFI for KrateData objects: 258 | #[doc(hidden)] 259 | #[no_mangle] 260 | pub unsafe extern "C" fn krate_data_new(ptr: *mut PyList) -> *mut KrateData { 261 | let p = PyList::from_ptr(ptr); 262 | let p: Vec = PyList::into(p); 263 | Box::into_raw(Box::new(KrateData::new(p))) 264 | } 265 | 266 | #[doc(hidden)] 267 | #[no_mangle] 268 | pub unsafe extern "C" fn krate_data_free(ptr: *mut KrateData) { 269 | if ptr.is_null() { 270 | return; 271 | } 272 | Box::from_raw(ptr); 273 | } 274 | 275 | #[doc(hidden)] 276 | #[no_mangle] 277 | pub extern "C" fn krate_data_len(krate: &KrateData) -> size_t { 278 | krate.collected.len() 279 | } 280 | 281 | #[doc(hidden)] 282 | #[no_mangle] 283 | pub extern "C" fn krate_data_iter(krate: &KrateData, idx: size_t) -> *mut PyString { 284 | match krate.iter_krate(idx as usize) { 285 | Some(val) => PyString::from(val).into_raw(), 286 | None => PyString::from("NO_IDX_ERROR").into_raw(), 287 | } 288 | } 289 | 290 | #[cfg(test)] 291 | mod parsing_tests { 292 | use super::*; 293 | 294 | #[test] 295 | #[ignore] 296 | fn parse_lib() { 297 | let path = std::env::home_dir() 298 | .unwrap() 299 | .join("workspace/sources/rustypy_debug/rust_code/src/lib.rs"); 300 | // let path_ori: std::path::PathBuf = std::env::current_dir().unwrap(); 301 | // let path: std::path::PathBuf = path_ori 302 | // .parent() 303 | // .unwrap() 304 | // .parent() 305 | // .unwrap() 306 | // .join("tests/rs_test_lib/lib.rs"); 307 | // the entry point to the library: 308 | let entry_point = PyString::from(path.to_str().unwrap().to_string()).into_raw(); 309 | let mut krate_data = KrateData::new(vec!["python_bind_".to_string()]); 310 | unsafe { 311 | let response = parse_src(entry_point, &mut krate_data); 312 | let response: String = PyString::from_ptr_to_string(response); 313 | assert!(!response.is_empty()); 314 | } 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /src/librustypy/macros.rs: -------------------------------------------------------------------------------- 1 | mod inner_types { 2 | /// Iterates over a a PyTuple and returns a corresponding Rust tuple. 3 | /// 4 | /// When destructured all inner data is moved out of the tuple which will retain the structure 5 | /// but will point to PyArg::None variant, so is safe to assume the PyTuple as consumed. 6 | /// 7 | /// Inner containers (ie. `PyList`) are converted to the respective Rust analog 8 | /// (ie. `Vec`) and require valid syntax for their respective unpack macro (ie. 9 | /// [unpack_pylist!](../rustypy/macro.unpack_pylist!.html)). 10 | /// 11 | /// # Examples 12 | /// 13 | /// Unpack a PyTuple which contains a two PyDict types with PyString keys 14 | /// and values of PyList: 15 | /// 16 | /// ``` 17 | /// # #[macro_use] extern crate rustypy; 18 | /// # fn main(){ 19 | /// # use rustypy::{PyDict, PyList, PyTuple, PyArg, PyString}; 20 | /// # use std::collections::HashMap; 21 | /// # let mut hm = HashMap::new(); 22 | /// # hm.insert(PyString::from("one"), vec![0_i32, 1, 2]); 23 | /// # hm.insert(PyString::from("two"), vec![3_i32, 2, 1]); 24 | /// # let mut pytuple = pytuple!(PyArg::PyDict(PyDict::from(hm.clone()).into_raw()), 25 | /// # PyArg::PyDict(PyDict::from(hm.clone()).into_raw())).into_raw(); 26 | /// // tuple from Python: ({"one": [0, 1, 3]}, {"two": [3, 2, 1]}) 27 | /// let unpacked = unpack_pytuple!(pytuple; ({PyDict{(PyString, PyList{I32 => i64})}}, 28 | /// {PyDict{(PyString, PyList{I32 => i64})}},)); 29 | /// # } 30 | /// ``` 31 | /// 32 | #[macro_export] 33 | macro_rules! unpack_pytuple { 34 | // FIXME: produces clippy warning on macro expansion due to usage `cnt` 35 | ($t:ident; ($($p:tt,)+) ) => {{ 36 | use rustypy::{PyArg, PyTuple}; 37 | use rustypy::pytypes::abort_and_exit; 38 | 39 | let mut cnt = 0; 40 | let mut pytuple = unsafe { PyTuple::from_ptr($t) }; 41 | ($( 42 | unpack_pytuple!(pytuple; cnt; elem: $p) 43 | ,)*) 44 | }}; 45 | ($t:ident; $i:ident; elem: ($($p:tt,)+)) => {{ 46 | let e = $t.replace_elem($i).unwrap(); 47 | match e { 48 | PyArg::PyTuple(val) => { 49 | $i += 1; 50 | if $i == 0 {}; // stub to remove warning... 51 | let mut val = unsafe { PyTuple::from_ptr(val) }; 52 | let mut cnt = 0; 53 | ($( 54 | unpack_pytuple!(val; cnt; elem: $p) 55 | ,)*) 56 | }, 57 | _ => abort_and_exit("failed while extracting a PyTuple inside a PyTuple"), 58 | } 59 | }}; 60 | ($t:ident; $i:ident; elem: {PyDict{$u:tt}}) => {{ 61 | let e = $t.replace_elem($i).unwrap(); 62 | match e { 63 | PyArg::PyDict(val) => { 64 | $i += 1; 65 | if $i == 0 {}; // stub to remove warning... 66 | unpack_pydict!(val; PyDict{$u}) 67 | }, 68 | _ => abort_and_exit("failed while extracting a PyDict inside a PyTuple"), 69 | } 70 | }}; 71 | ($t:ident; $i:ident; elem: {PyList{$($u:tt)*}}) => {{ 72 | let e = $t.replace_elem($i).unwrap(); 73 | match e { 74 | PyArg::PyList(val) => { 75 | $i += 1; 76 | if $i == 0 {}; // stub to remove warning... 77 | unpack_pylist!(val; PyList{$($u)*}) 78 | }, 79 | _ => abort_and_exit("failed while extracting a PyList inside a PyTuple"), 80 | } 81 | }}; 82 | ($t:ident; $i:ident; elem: PyBool) => {{ 83 | let e = $t.replace_elem($i).unwrap(); 84 | match e { 85 | PyArg::PyBool(val) => { 86 | $i += 1; 87 | if $i == 0 {}; // stub to remove warning... 88 | val.to_bool() 89 | }, 90 | _ => abort_and_exit("failed while extracting a PyBool inside a PyTuple"), 91 | } 92 | }}; 93 | ($t:ident; $i:ident; elem: PyString) => {{ 94 | let e = $t.replace_elem($i).unwrap(); 95 | match e { 96 | PyArg::PyString(val) => { 97 | $i += 1; 98 | if $i == 0 {}; // stub to remove warning... 99 | val.to_string() 100 | }, 101 | _ => abort_and_exit("failed while extracting a PyString inside a PyTuple"), 102 | } 103 | }}; 104 | ($t:ident; $i:ident; elem: I64) => {{ 105 | let e = $t.replace_elem($i).unwrap(); 106 | match e { 107 | PyArg::I64(val) => { 108 | $i += 1; 109 | if $i == 0 {}; // stub to remove warning... 110 | val 111 | }, 112 | _ => abort_and_exit("failed while extracting a i64 inside a PyTuple"), 113 | } 114 | }}; 115 | ($t:ident; $i:ident; elem: I32) => {{ 116 | let e = $t.replace_elem($i).unwrap(); 117 | match e { 118 | PyArg::I32(val) => { 119 | $i += 1; 120 | if $i == 0 {}; // stub to remove warning... 121 | val.clone() 122 | }, 123 | _ => abort_and_exit("failed while extracting a i32 inside a PyTuple"), 124 | } 125 | }}; 126 | ($t:ident; $i:ident; elem: I16) => {{ 127 | let e = $t.replace_elem($i).unwrap(); 128 | match e { 129 | PyArg::I16(val) => { 130 | $i += 1; 131 | if $i == 0 {}; // stub to remove warning... 132 | val 133 | }, 134 | _ => abort_and_exit("failed while extracting a i16 inside a PyTuple"), 135 | } 136 | }}; 137 | ($t:ident; $i:ident; elem: I8) => {{ 138 | let e = $t.replace_elem($i).unwrap(); 139 | match e { 140 | PyArg::I8(val) => { 141 | $i += 1; 142 | if $i == 0 {}; // stub to remove warning... 143 | val 144 | }, 145 | _ => abort_and_exit("failed while extracting a i8 inside a PyTuple"), 146 | } 147 | }}; 148 | ($t:ident; $i:ident; elem: U32) => {{ 149 | let e = $t.replace_elem($i).unwrap(); 150 | match e { 151 | PyArg::U32(val) => { 152 | $i += 1; 153 | if $i == 0 {}; // stub to remove warning... 154 | val 155 | }, 156 | _ => abort_and_exit("failed while extracting a u32 inside a PyTuple"), 157 | } 158 | }}; 159 | ($t:ident; $i:ident; elem: U16) => {{ 160 | let e = $t.replace_elem($i).unwrap(); 161 | match e { 162 | PyArg::U16(val) => { 163 | $i += 1; 164 | if $i == 0 {}; // stub to remove warning... 165 | val 166 | }, 167 | _ => abort_and_exit("failed while extracting a u16 inside a PyTuple"), 168 | } 169 | }}; 170 | ($t:ident; $i:ident; elem: U8) => {{ 171 | let e = $t.replace_elem($i).unwrap(); 172 | match e { 173 | PyArg::U8(val) => { 174 | $i += 1; 175 | if $i == 0 {}; // stub to remove warning... 176 | val 177 | }, 178 | _ => abort_and_exit("failed while extracting a u8 inside a PyTuple"), 179 | } 180 | }}; 181 | ($t:ident; $i:ident; elem: F32) => {{ 182 | let e = $t.replace_elem($i).unwrap(); 183 | match e { 184 | PyArg::F32(val) => { 185 | $i += 1; 186 | if $i == 0 {}; // stub to remove warning... 187 | val 188 | }, 189 | _ => abort_and_exit("failed while extracting a f32 inside a PyTuple"), 190 | } 191 | }}; 192 | ($t:ident; $i:ident; elem: F64) => {{ 193 | let e = $t.replace_elem($i).unwrap(); 194 | match e { 195 | PyArg::F64(val) => { 196 | $i += 1; 197 | if $i == 0 {}; // stub to remove warning... 198 | val 199 | }, 200 | _ => abort_and_exit("failed while extracting a f64 inside a PyTuple"), 201 | } 202 | }}; 203 | } 204 | 205 | /// Consumes a `PyList` content as recived from Python (raw pointer) 206 | /// and returns a `Vec` from it, no copies are performed in the process. 207 | /// 208 | /// All inner elements are moved out if possible, if not (like with PyTuples) are copied. 209 | /// PyTuple variants are destructured into Rust tuples which contain the appropiate Rust types 210 | /// (valid syntax for [unpack_pytuple!](../rustypy/macro.unpack_pytuple!.html) macro must 211 | /// be provided). The same for other container types (inner PyList, PyDict, etc.). 212 | /// 213 | /// # Examples 214 | /// 215 | /// A simple PyList which contains PyString types:: 216 | /// 217 | /// ``` 218 | /// # #[macro_use] extern crate rustypy; 219 | /// # fn main(){ 220 | /// use rustypy::{PyList, PyString}; 221 | /// let string_list = PyList::from(vec!["Python", "in", "Rust"]).into_raw(); 222 | /// let unpacked = unpack_pylist!(string_list; PyList{PyString => PyString}); 223 | /// # } 224 | /// ``` 225 | /// 226 | /// And an other with i32: 227 | /// 228 | /// ``` 229 | /// # #[macro_use] extern crate rustypy; 230 | /// # fn main(){ 231 | /// # use rustypy::PyList; 232 | /// # let int_list = PyList::from(vec![1i32; 5]).into_raw(); 233 | /// // list from python: [1, 1, 1, 1, 1] 234 | /// let unpacked = unpack_pylist!(int_list; PyList{I32 => i32}); 235 | /// # } 236 | /// ``` 237 | /// 238 | /// It can contain nested containers. A PyList which contains PyTuples which contain a list 239 | /// of i64 PyTuples and a single f32: 240 | /// 241 | /// ``` 242 | /// # #[macro_use] extern crate rustypy; 243 | /// # fn main(){ 244 | /// # use rustypy::{PyList, PyArg}; 245 | /// # let list = PyList::from(vec![ 246 | /// # pytuple!(PyArg::PyList(PyList::from(vec![ 247 | /// # pytuple!(PyArg::I64(1), PyArg::I64(2), PyArg::I64(3))]).into_raw()), 248 | /// # PyArg::F32(0.1)), 249 | /// # pytuple!(PyArg::PyList(PyList::from(vec![ 250 | /// # pytuple!(PyArg::I64(3), PyArg::I64(2), PyArg::I64(1))]).into_raw()), 251 | /// # PyArg::F32(0.2)) 252 | /// # ]).into_raw(); 253 | /// // list from Python: [([(i64; 3)], f32)] 254 | /// let unpacked = unpack_pylist!(list; 255 | /// PyList{ 256 | /// PyTuple{( 257 | /// {PyList{PyTuple{(I64, I64, I64,)}}}, F32, 258 | /// )} 259 | /// }); 260 | /// assert_eq!(vec![(vec![(1, 2, 3,)], 0.1), (vec![(3, 2, 1,)], 0.2)], unpacked); 261 | /// # } 262 | /// ``` 263 | /// 264 | #[macro_export] 265 | macro_rules! unpack_pylist { 266 | ( $pylist:ident; PyList { $o:tt { $($t:tt)* } } ) => {{ 267 | use rustypy::{PyList, PyArg}; 268 | use rustypy::pytypes::abort_and_exit; 269 | use std::collections::VecDeque; 270 | 271 | let mut unboxed = unsafe { PyList::from_ptr($pylist) }; 272 | let mut list = VecDeque::with_capacity(unboxed.len()); 273 | for _ in 0..unboxed.len() { 274 | match unboxed.pop() { 275 | Some(PyArg::$o(val)) => { 276 | let inner = unpack_pylist!(val; $o { $($t)* }); 277 | list.push_front(inner); 278 | }, 279 | Some(_) => abort_and_exit("failed while converting pylist to vec"), 280 | None => {} 281 | } 282 | }; 283 | Vec::from(list) 284 | }}; 285 | ( $pytuple:ident; PyTuple { $t:tt } ) => {{ 286 | unpack_pytuple!($pytuple; $t) 287 | }}; 288 | ( $pylist:ident; PyList{$t:tt => $type_:ty} ) => {{ 289 | use rustypy::{PyList, PyArg}; 290 | use rustypy::pytypes::abort_and_exit; 291 | use std::collections::VecDeque; 292 | 293 | let mut unboxed = unsafe { PyList::from_ptr($pylist) }; 294 | let mut list = VecDeque::with_capacity(unboxed.len()); 295 | for _ in 0..unboxed.len() { 296 | match unboxed.pop() { 297 | Some(PyArg::$t(val)) => { list.push_front(<$type_>::from(val)); }, 298 | Some(_) => abort_and_exit("failed while converting pylist to vec"), 299 | None => {} 300 | } 301 | }; 302 | Vec::from(list) 303 | }}; 304 | ( $pydict:ident; PyDict{$t:tt} ) => {{ 305 | unpack_pydict!( $pydict; PyDict{$t} ) 306 | }}; 307 | } 308 | 309 | /// Consumes the content of a `PyDict` as received from Python (raw pointer) 310 | /// and returns a `HashMap` from it, no copies are performed in the process. 311 | /// 312 | /// All inner elements are moved out if possible, if not (like with PyTuples) are copied. 313 | /// PyTuple variants are destructured into Rust tuples which contain the appropiate Rust types 314 | /// (valid syntax for [unpack_pytuple!](../rustypy/macro.unpack_pytuple!.html) macro must 315 | /// be provided). The same happens with other container types (inner PyList, PyDict, etc.). 316 | /// 317 | /// # Examples 318 | /// 319 | /// A simple PyDict with i32 keys which contains PyString types:: 320 | /// 321 | /// ``` 322 | /// # #[macro_use] extern crate rustypy; 323 | /// # fn main(){ 324 | /// # use std::iter::FromIterator; 325 | /// use rustypy::{PyDict, PyString}; 326 | /// use std::collections::HashMap; 327 | /// 328 | /// let mut hm = HashMap::from_iter(vec![(0_i32, "Hello"), (1_i32, " "), (2_i32, "World!")]); 329 | /// let dict = PyDict::from(hm).into_raw(); 330 | /// let unpacked = unpack_pydict!(dict; PyDict{(i32, PyString => String)}); 331 | /// # } 332 | /// ``` 333 | /// 334 | /// With nested container types: 335 | /// 336 | /// ``` 337 | /// # #[macro_use] extern crate rustypy; 338 | /// # #[allow(unused_variables)] 339 | /// # fn main(){ 340 | /// # use rustypy::{PyDict, PyString, PyArg}; 341 | /// # use std::collections::HashMap; 342 | /// # let mut hm0 = HashMap::new(); 343 | /// # for &(k, v) in &[(0_i32, "Hello"), (1, " "), (2, "World!")] { 344 | /// # hm0.insert(k, v); 345 | /// # } 346 | /// # let mut hm1 = HashMap::new(); 347 | /// # for i in 0..3_i64 { 348 | /// # let k = format!("#{}", i); 349 | /// # let v = PyArg::from(hm0.clone()); 350 | /// # let l = PyArg::from(vec![0_i64; 3]); 351 | /// # hm1.insert(PyString::from(k), pytuple!(l, v)); 352 | /// # } 353 | /// # let dict = PyDict::from(hm1).into_raw(); 354 | /// // dict from Python: {str: ([u64], {i32: str})} as *mut size_t 355 | /// let unpacked = unpack_pydict!(dict; 356 | /// PyDict{(PyString, PyTuple{({PyList{I64 => i64}}, 357 | /// {PyDict{(i32, PyString => String)}},)})} 358 | /// ); 359 | /// # } 360 | /// ``` 361 | /// 362 | #[macro_export] 363 | macro_rules! unpack_pydict { 364 | ( $pydict:ident; PyDict{($kt:ty, $o:tt { $($t:tt)* })} ) => {{ 365 | use rustypy::{PyArg, PyDict}; 366 | use rustypy::pytypes::abort_and_exit; 367 | use std::collections::HashMap; 368 | 369 | let mut unboxed = unsafe { *(Box::from_raw($pydict as *mut PyDict<$kt>)) }; 370 | let mut dict = HashMap::new(); 371 | for (k, v) in unboxed.drain() { 372 | match v { 373 | PyArg::$o(val) => { 374 | let inner = unpack_pydict!(val; $o { $($t)* }); 375 | dict.insert(k, inner); 376 | } 377 | _ => abort_and_exit("failed while converting PyDict to HashMap") 378 | } 379 | } 380 | dict 381 | }}; 382 | ( $pytuple:ident; PyTuple { $t:tt } ) => {{ 383 | unpack_pytuple!($pytuple; $t) 384 | }}; 385 | ( $pylist:ident; PyList{ $($u:tt)* } ) => {{ 386 | unpack_pylist!( $pylist; PyList{ $($u)* } ) 387 | }}; 388 | ( $pydict:ident; PyDict{($kt:ty, $t:tt => $type_:ty)} ) => {{ 389 | use rustypy::{PyArg, PyDict}; 390 | use rustypy::pytypes::abort_and_exit; 391 | use std::collections::HashMap; 392 | 393 | let mut unboxed = unsafe { *(Box::from_raw($pydict as *mut PyDict<$kt>)) }; 394 | let mut dict = HashMap::new(); 395 | for (k, v) in unboxed.drain() { 396 | match v { 397 | PyArg::$t(val) => { dict.insert(k, <$type_>::from(val)); }, 398 | _ => abort_and_exit("failed while converting PyDict to HashMap"), 399 | } 400 | } 401 | dict 402 | }}; 403 | } 404 | } 405 | 406 | /// Converts python data to `std` Rust collections or tuples, moving the data: 407 | /// * Consumes the content of a `PyDict` as received from Python (raw pointer) 408 | /// and returns a `HashMap` from it. 409 | /// * Consumes a `PyList` content as recived from Python (raw pointer) 410 | /// and returns a `Vec` from it. 411 | /// * Iterates over a a PyTuple and returns a corresponding Rust tuple. Moves the data 412 | /// leaving an empty PyTuple. 413 | /// 414 | /// Inner containers (ie. `PyList`) are converted to the respective Rust analog 415 | /// (ie. `Vec`) 416 | /// 417 | /// # Examples 418 | /// 419 | /// Unpack a PyTuple from Python: 420 | /// 421 | /// ``` 422 | /// # #[macro_use] extern crate rustypy; 423 | /// # fn main(){ 424 | /// # use rustypy::{PyDict, PyList, PyTuple, PyArg, PyString}; 425 | /// # use std::collections::HashMap; 426 | /// # let mut hm = HashMap::new(); 427 | /// # hm.insert(PyString::from("one"), vec![0_i32, 1, 2]); 428 | /// # hm.insert(PyString::from("two"), vec![3_i32, 2, 1]); 429 | /// # let mut pytuple = pytuple!(PyArg::PyDict(PyDict::from(hm.clone()).into_raw()), 430 | /// # PyArg::PyDict(PyDict::from(hm.clone()).into_raw())).into_raw(); 431 | /// // tuple from Python: ({"one": [0, 1, 3]}, {"two": [3, 2, 1]}) 432 | /// let unpacked = unpack_pytuple!(pytuple; ({PyDict{(PyString, PyList{I32 => i64})}}, 433 | /// {PyDict{(PyString, PyList{I32 => i64})}},)); 434 | /// # } 435 | /// ``` 436 | /// 437 | /// Unpack a PyList from Python: 438 | /// 439 | /// ``` 440 | /// # #[macro_use] extern crate rustypy; 441 | /// # fn main(){ 442 | /// # use rustypy::PyList; 443 | /// # let int_list = PyList::from(vec![1i32; 5]).into_raw(); 444 | /// // list from python: [1, 1, 1, 1, 1] 445 | /// let unpacked = unpack_pytype!(int_list; PyList{I32 => i32}); 446 | /// # } 447 | /// ``` 448 | /// 449 | /// Unpack a PyDict from Python: 450 | /// 451 | /// ``` 452 | /// # #[macro_use] extern crate rustypy; 453 | /// # fn main(){ 454 | /// # use std::iter::FromIterator; 455 | /// # use rustypy::{PyDict, PyString}; 456 | /// # use std::collections::HashMap; 457 | /// # 458 | /// # let mut hm = HashMap::from_iter(vec![(0_i32, "Hello"), (1_i32, " "), (2_i32, "World!")]); 459 | /// # let dict = PyDict::from(hm).into_raw(); 460 | /// let unpacked = unpack_pytype!(dict; PyDict{(i32, PyString => String)}); 461 | /// # } 462 | /// ``` 463 | /// 464 | /// You can combine different containers and nest them: 465 | /// 466 | /// ``` 467 | /// # #[macro_use] extern crate rustypy; 468 | /// # #[allow(unused_variables)] 469 | /// # fn main(){ 470 | /// # use rustypy::{PyDict, PyString, PyArg}; 471 | /// # use std::collections::HashMap; 472 | /// # let mut hm0 = HashMap::new(); 473 | /// # for &(k, v) in &[(0_i32, "Hello"), (1, " "), (2, "World!")] { 474 | /// # hm0.insert(k, v); 475 | /// # } 476 | /// # let mut hm1 = HashMap::new(); 477 | /// # for i in 0..3_i64 { 478 | /// # let k = format!("#{}", i); 479 | /// # let v = PyArg::from(hm0.clone()); 480 | /// # let l = PyArg::from(vec![0_i64; 3]); 481 | /// # hm1.insert(PyString::from(k), pytuple!(l, v)); 482 | /// # } 483 | /// # let dict = PyDict::from(hm1).into_raw(); 484 | /// // dict from Python: {str: ([u64], {i32: str})} as *mut size_t 485 | /// let unpacked = unpack_pytype!(dict; 486 | /// PyDict{(PyString, PyTuple{({PyList{I64 => i64}}, 487 | /// {PyDict{(i32, PyString => String)}},)})} 488 | /// ); 489 | /// # } 490 | /// ``` 491 | #[macro_export] 492 | macro_rules! unpack_pytype { 493 | ($t:ident; ($($p:tt,)+)) => { 494 | unpack_pytuple!($t; ($($p,)*)) 495 | }; 496 | ($pylist:ident; PyList{$t:tt => $type_:ty}) => { 497 | unpack_pylist!($pylist; PyList{$t => $type_}) 498 | }; 499 | ($pylist:ident; PyList { $o:tt { $($t:tt)* } }) => { 500 | unpack_pylist!($pylist; PyList { $o { $($t)* } }) 501 | }; 502 | ($pydict:ident; PyDict{($kt:ty, $o:tt { $($t:tt)* })}) => { 503 | unpack_pydict!($pydict; PyDict{($kt, $o { $($t)* })}) 504 | }; 505 | ($pydict:ident; PyDict{($kt:ty, $t:tt => $type_:ty)}) => { 506 | unpack_pydict!($pydict; PyDict{($kt, $t => $type_)}) 507 | }; 508 | } 509 | 510 | #[cfg(test)] 511 | mod tests { 512 | use crate as rustypy; 513 | use rustypy::*; 514 | 515 | #[test] 516 | fn pytuple_macro() { 517 | let pytuple = pytuple!( 518 | PyArg::PyBool(PyBool::from(false)), 519 | PyArg::PyString(PyString::from("test")), 520 | PyArg::I64(55i64) 521 | ) 522 | .into_raw(); 523 | let unpacked = unpack_pytype!(pytuple; (PyBool, PyString, I64,)); 524 | assert_eq!((false, String::from("test"), 55i64), unpacked); 525 | } 526 | 527 | #[test] 528 | fn unpack_pylist_macro() { 529 | use std::iter::FromIterator; 530 | let nested = PyList::from_iter(vec![ 531 | pytuple!( 532 | PyArg::PyList(PyList::from_iter(vec![1i32, 2, 3]).into_raw()), 533 | PyArg::F32(0.1) 534 | ), 535 | pytuple!( 536 | PyArg::PyList(PyList::from_iter(vec![3i32, 2, 1]).into_raw()), 537 | PyArg::F32(0.2) 538 | ), 539 | ]) 540 | .into_raw(); 541 | let unpacked = unpack_pytype!(nested; PyList{PyTuple{({PyList{I32 => i32}}, F32,)}}); 542 | assert_eq!(vec![(vec![1, 2, 3], 0.1), (vec![3, 2, 1], 0.2)], unpacked); 543 | } 544 | } 545 | -------------------------------------------------------------------------------- /src/librustypy/pytypes/mod.rs: -------------------------------------------------------------------------------- 1 | //! Types for interfacing with Python. 2 | 3 | use libc::{c_char, size_t}; 4 | 5 | use std::collections::HashMap; 6 | use std::convert::AsRef; 7 | use std::hash::Hash; 8 | 9 | #[doc(hidden)] 10 | #[inline(never)] 11 | #[cold] 12 | pub fn abort_and_exit(msg: &str) -> ! { 13 | use std::io::{stderr, stdout, Write}; 14 | use std::process; 15 | 16 | fn write(mut handle: T, msg: &str) { 17 | write!(&mut handle, "\nrustypy: failed abrupty!").unwrap(); 18 | write!( 19 | &mut handle, 20 | "rustypy: aborted process, tried to extract one type, but found an other \ 21 | instead:\n {}\n", 22 | msg 23 | ) 24 | .unwrap(); 25 | handle.flush().unwrap(); 26 | } 27 | 28 | write(stderr(), msg); 29 | write(stdout(), msg); 30 | process::exit(1); 31 | } 32 | 33 | pub mod pybool; 34 | pub mod pydict; 35 | pub mod pylist; 36 | pub mod pystring; 37 | pub mod pytuple; 38 | 39 | use self::pybool::PyBool; 40 | use self::pydict::{PyDict, PyDictKey}; 41 | use self::pylist::PyList; 42 | use self::pystring::PyString; 43 | use self::pytuple::PyTuple; 44 | 45 | /// Enum type used to construct PyTuple and PyList types. All the kinds supported in Python 46 | /// are included here. 47 | /// 48 | /// In Python, conversion of floats default to double precision unless explicitly stated 49 | /// adding the Float custom rustypy type to the return type signature. 50 | /// 51 | /// ```Python 52 | /// from rustypy.rswrapper import Double, Float 53 | /// bindings.my_binded_func.restype = Float 54 | /// bindings.my_binded_func.restype = Double 55 | /// ``` 56 | /// 57 | /// Likewise, all 'int' types are converted to signed 64-bit integers by default. 58 | #[derive(Clone, Debug, PartialEq)] 59 | pub enum PyArg { 60 | I64(i64), 61 | I32(i32), 62 | I16(i16), 63 | I8(i8), 64 | U64(u64), 65 | U32(u32), 66 | U16(u16), 67 | U8(u8), 68 | F32(f32), 69 | F64(f64), 70 | PyBool(PyBool), 71 | PyString(PyString), 72 | PyTuple(*mut PyTuple), 73 | PyList(*mut PyList), 74 | PyDict(*mut size_t), 75 | None, 76 | } 77 | 78 | impl PyArg { 79 | pub fn into_raw(self) -> *mut PyArg { 80 | Box::into_raw(Box::new(self)) 81 | } 82 | } 83 | 84 | macro_rules! pyarg_conversions { 85 | ($type:ty; $variant:path; $repr:expr) => { 86 | impl AsRef<$type> for PyArg { 87 | fn as_ref(&self) -> &$type { 88 | match *self { 89 | $variant(ref v) => v, 90 | _ => { 91 | let msg = format!("expected a {} while destructuring PyArg enum", $repr); 92 | abort_and_exit(msg.as_str()); 93 | } 94 | } 95 | } 96 | } 97 | 98 | impl From<$type> for PyArg { 99 | fn from(a: $type) -> PyArg { 100 | $variant(a) 101 | } 102 | } 103 | 104 | impl From for $type { 105 | fn from(a: PyArg) -> $type { 106 | match a { 107 | $variant(v) => v, 108 | _ => { 109 | let msg = format!("expected a {} while destructuring PyArg enum", $repr); 110 | abort_and_exit(msg.as_str()); 111 | } 112 | } 113 | } 114 | } 115 | }; 116 | (BOXED $type:ty; $variant:path; $repr:expr) => { 117 | impl AsRef<$type> for PyArg { 118 | fn as_ref(&self) -> &$type { 119 | match *self { 120 | $variant(v) => unsafe { &*v }, 121 | _ => { 122 | let msg = format!("expected a {} while destructuring PyArg enum", $repr); 123 | abort_and_exit(msg.as_str()); 124 | } 125 | } 126 | } 127 | } 128 | 129 | impl From<$type> for PyArg { 130 | fn from(a: $type) -> PyArg { 131 | $variant(a.into_raw()) 132 | } 133 | } 134 | 135 | impl From for $type { 136 | fn from(a: PyArg) -> $type { 137 | match a { 138 | $variant(v) => unsafe { *(Box::from_raw(v)) }, 139 | _ => { 140 | let msg = format!("expected a {} while destructuring PyArg enum", $repr); 141 | abort_and_exit(msg.as_str()); 142 | } 143 | } 144 | } 145 | } 146 | }; 147 | } 148 | 149 | pyarg_conversions!(i8; PyArg::I8; "i8"); 150 | pyarg_conversions!(i16; PyArg::I16; "i16"); 151 | pyarg_conversions!(i32; PyArg::I32; "i32"); 152 | pyarg_conversions!(i64; PyArg::I64; "i64"); 153 | pyarg_conversions!(u8; PyArg::U8; "u8"); 154 | pyarg_conversions!(u16; PyArg::U16; "u16"); 155 | pyarg_conversions!(u32; PyArg::U32; "u32"); 156 | pyarg_conversions!(u64; PyArg::U64; "u64"); 157 | pyarg_conversions!(f32; PyArg::F32; "f32"); 158 | pyarg_conversions!(f64; PyArg::F64; "f64"); 159 | pyarg_conversions!(PyBool; PyArg::PyBool; "PyBool"); 160 | pyarg_conversions!(PyString; PyArg::PyString; "PyString"); 161 | pyarg_conversions!(BOXED PyTuple; PyArg::PyTuple; "PyTuple"); 162 | pyarg_conversions!(BOXED PyList; PyArg::PyList; "PyList"); 163 | 164 | impl AsRef> for PyArg 165 | where 166 | K: Eq + Hash + PyDictKey, 167 | { 168 | fn as_ref(&self) -> &PyDict { 169 | match *self { 170 | PyArg::PyDict(dict) => unsafe { &*(dict as *mut PyDict) as &PyDict }, 171 | _ => abort_and_exit("expected a PyDict while destructuring PyArg enum"), 172 | } 173 | } 174 | } 175 | 176 | // Conversions: PyArg from 177 | 178 | impl<'a> From<&'a str> for PyArg { 179 | fn from(a: &str) -> PyArg { 180 | PyArg::PyString(PyString::from(a)) 181 | } 182 | } 183 | 184 | impl From for PyArg { 185 | fn from(a: String) -> PyArg { 186 | PyArg::PyString(PyString::from(a)) 187 | } 188 | } 189 | 190 | impl From for PyArg { 191 | fn from(a: bool) -> PyArg { 192 | PyArg::PyBool(PyBool::from(a)) 193 | } 194 | } 195 | 196 | impl<'a> From<&'a bool> for PyArg { 197 | fn from(a: &'a bool) -> PyArg { 198 | PyArg::PyBool(PyBool::from(a)) 199 | } 200 | } 201 | 202 | impl From> for PyArg 203 | where 204 | PyArg: From, 205 | { 206 | fn from(a: Vec) -> PyArg { 207 | PyArg::PyList(PyList::from(a).into_raw()) 208 | } 209 | } 210 | 211 | impl From> for PyArg 212 | where 213 | K: Eq + Hash + PyDictKey, 214 | { 215 | fn from(a: PyDict) -> PyArg { 216 | PyArg::PyDict(a.into_raw()) 217 | } 218 | } 219 | 220 | impl From> for PyArg 221 | where 222 | PyArg: From, 223 | K: Eq + Hash + PyDictKey, 224 | { 225 | fn from(a: HashMap) -> PyArg { 226 | let dict = PyDict::from(a); 227 | PyArg::PyDict(dict.into_raw()) 228 | } 229 | } 230 | 231 | // Conversions from PyArg to 232 | 233 | impl From for String { 234 | fn from(a: PyArg) -> String { 235 | match a { 236 | PyArg::PyString(v) => v.to_string(), 237 | _ => abort_and_exit("expected a PyString while destructuring PyArg enum"), 238 | } 239 | } 240 | } 241 | 242 | impl From for PyDict 243 | where 244 | K: Eq + Hash + PyDictKey, 245 | { 246 | fn from(a: PyArg) -> PyDict { 247 | match a { 248 | PyArg::PyDict(v) => unsafe { *(Box::from_raw(v as *mut PyDict)) }, 249 | _ => abort_and_exit("expected a PyDict while destructuring PyArg enum"), 250 | } 251 | } 252 | } 253 | 254 | // From types: 255 | 256 | #[doc(hidden)] 257 | #[no_mangle] 258 | pub extern "C" fn pyarg_from_int(e: i64) -> *mut PyArg { 259 | Box::into_raw(Box::new(PyArg::I64(e))) 260 | } 261 | 262 | #[doc(hidden)] 263 | #[no_mangle] 264 | pub extern "C" fn pyarg_from_ulonglong(e: u64) -> *mut PyArg { 265 | Box::into_raw(Box::new(PyArg::U64(e))) 266 | } 267 | 268 | #[doc(hidden)] 269 | #[no_mangle] 270 | pub extern "C" fn pyarg_from_float(e: f32) -> *mut PyArg { 271 | Box::into_raw(Box::new(PyArg::F32(e))) 272 | } 273 | 274 | #[doc(hidden)] 275 | #[no_mangle] 276 | pub extern "C" fn pyarg_from_double(e: f64) -> *mut PyArg { 277 | Box::into_raw(Box::new(PyArg::F64(e))) 278 | } 279 | 280 | #[doc(hidden)] 281 | #[no_mangle] 282 | pub extern "C" fn pyarg_from_bool(e: i8) -> *mut PyArg { 283 | let e = PyBool::from(e); 284 | Box::into_raw(Box::new(PyArg::PyBool(e))) 285 | } 286 | 287 | #[doc(hidden)] 288 | #[no_mangle] 289 | pub unsafe extern "C" fn pyarg_from_str(e: *const c_char) -> *mut PyArg { 290 | let e = PyString::from_raw(e); 291 | Box::into_raw(Box::new(PyArg::PyString(e))) 292 | } 293 | 294 | #[doc(hidden)] 295 | #[no_mangle] 296 | pub extern "C" fn pyarg_from_pytuple(e: *mut PyTuple) -> *mut PyArg { 297 | //let e = unsafe { PyTuple::from_ptr(e) }; 298 | Box::into_raw(Box::new(PyArg::PyTuple(e))) 299 | } 300 | 301 | #[doc(hidden)] 302 | #[no_mangle] 303 | pub extern "C" fn pyarg_from_pylist(e: *mut PyList) -> *mut PyArg { 304 | //let e = unsafe { PyList::from_ptr(e) }; 305 | Box::into_raw(Box::new(PyArg::PyList(e))) 306 | } 307 | 308 | #[doc(hidden)] 309 | #[no_mangle] 310 | pub extern "C" fn pyarg_from_pydict(e: *mut size_t) -> *mut PyArg { 311 | Box::into_raw(Box::new(PyArg::PyDict(e))) 312 | } 313 | 314 | // Extract owned args, no copies: 315 | #[doc(hidden)] 316 | #[no_mangle] 317 | pub unsafe extern "C" fn pyarg_extract_owned_int(e: *mut PyArg) -> i64 { 318 | let e = *(Box::from_raw(e)); 319 | match e { 320 | PyArg::I64(val) => val, 321 | PyArg::I32(val) => i64::from(val), 322 | PyArg::I16(val) => i64::from(val), 323 | PyArg::I8(val) => i64::from(val), 324 | PyArg::U32(val) => i64::from(val), 325 | PyArg::U16(val) => i64::from(val), 326 | PyArg::U8(val) => i64::from(val), 327 | _ => abort_and_exit( 328 | "failed while trying to extract an integer type of i64 or \ 329 | less", 330 | ), 331 | } 332 | } 333 | 334 | #[doc(hidden)] 335 | #[no_mangle] 336 | pub unsafe extern "C" fn pyarg_extract_owned_ulonglong(e: *mut PyArg) -> u64 { 337 | let e = *(Box::from_raw(e)); 338 | match e { 339 | PyArg::U64(val) => val, 340 | _ => abort_and_exit("failed while trying to extract an u64"), 341 | } 342 | } 343 | 344 | #[doc(hidden)] 345 | #[no_mangle] 346 | pub unsafe extern "C" fn pyarg_extract_owned_float(e: *mut PyArg) -> f32 { 347 | let e = *(Box::from_raw(e)); 348 | match e { 349 | PyArg::F32(val) => val, 350 | _ => abort_and_exit("failed while trying to extract an f32"), 351 | } 352 | } 353 | 354 | #[doc(hidden)] 355 | #[no_mangle] 356 | pub unsafe extern "C" fn pyarg_extract_owned_double(e: *mut PyArg) -> f64 { 357 | let e = *(Box::from_raw(e)); 358 | match e { 359 | PyArg::F64(val) => val, 360 | _ => abort_and_exit("failed while trying to extract an f64"), 361 | } 362 | } 363 | 364 | #[doc(hidden)] 365 | #[no_mangle] 366 | pub unsafe extern "C" fn pyarg_extract_owned_bool(e: *mut PyArg) -> *mut PyBool { 367 | let e = *(Box::from_raw(e)); 368 | match e { 369 | PyArg::PyBool(val) => val.into_raw(), 370 | _ => abort_and_exit("failed while trying to extract a PyBool"), 371 | } 372 | } 373 | 374 | #[doc(hidden)] 375 | #[no_mangle] 376 | pub unsafe extern "C" fn pyarg_extract_owned_str(e: *mut PyArg) -> *mut PyString { 377 | let e = *(Box::from_raw(e)); 378 | match e { 379 | PyArg::PyString(val) => val.into_raw(), 380 | _ => abort_and_exit("failed while trying to extract a PyString"), 381 | } 382 | } 383 | 384 | #[doc(hidden)] 385 | #[no_mangle] 386 | pub unsafe extern "C" fn pyarg_extract_owned_tuple(e: *mut PyArg) -> *mut PyTuple { 387 | let e = *(Box::from_raw(e)); 388 | match e { 389 | PyArg::PyTuple(val) => val, 390 | _ => abort_and_exit("failed while trying to extract a PyTuple"), 391 | } 392 | } 393 | 394 | #[doc(hidden)] 395 | #[no_mangle] 396 | pub unsafe extern "C" fn pyarg_extract_owned_list(e: *mut PyArg) -> *mut PyList { 397 | let e = *(Box::from_raw(e)); 398 | match e { 399 | PyArg::PyList(val) => val, 400 | _ => abort_and_exit("failed while trying to extract a PyList"), 401 | } 402 | } 403 | 404 | #[doc(hidden)] 405 | #[no_mangle] 406 | pub unsafe extern "C" fn pyarg_extract_owned_dict(e: *mut PyArg) -> *mut size_t { 407 | let e = *(Box::from_raw(e)); 408 | match e { 409 | PyArg::PyDict(val) => val, 410 | _ => abort_and_exit("failed while trying to extract a PyDict"), 411 | } 412 | } 413 | -------------------------------------------------------------------------------- /src/librustypy/pytypes/pybool.rs: -------------------------------------------------------------------------------- 1 | //! Analog to a Python boolean type. 2 | //! 3 | //! It supports & and | operators, and comparison to Rust bool types. 4 | //! To return to Python use the ```into_raw``` method and return a raw pointer. 5 | //! 6 | //! # Safety 7 | //! You can convert a raw pointer to a bool type with ```from_ptr_into_bool``` method, 8 | //! or to a ```&PyBool``` with ```from_ptr``` method. Those operations are unsafe as they require 9 | //! dereferencing a raw pointer. 10 | //! 11 | //! # Examples 12 | //! 13 | //! ``` 14 | //! use rustypy::PyBool; 15 | //! let pybool = PyBool::from(true); 16 | //! assert_eq!(pybool, true); 17 | //! 18 | //! // prepare to return to Python: 19 | //! let ptr = pybool.into_raw(); 20 | //! // convert from raw pointer to a bool 21 | //! let rust_bool = unsafe { PyBool::from_ptr_into_bool(ptr) }; 22 | //! ``` 23 | use libc::c_char; 24 | 25 | use std::convert::From; 26 | use std::ops::{BitAnd, BitOr, Not}; 27 | 28 | /// Analog to a Python boolean type. 29 | /// 30 | /// Read the [module docs](index.html) for more information. 31 | #[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)] 32 | pub struct PyBool { 33 | val: i8, 34 | } 35 | 36 | impl PyBool { 37 | /// Get a PyBool from a previously boxed raw pointer. 38 | pub unsafe fn from_ptr(ptr: *mut PyBool) -> PyBool { 39 | *(Box::from_raw(ptr)) 40 | } 41 | 42 | /// Creates a bool from a raw pointer to a PyBool. 43 | pub unsafe fn from_ptr_into_bool(ptr: *mut PyBool) -> bool { 44 | let ptr: &PyBool = &*ptr; 45 | match ptr.val { 46 | 0 => false, 47 | _ => true, 48 | } 49 | } 50 | 51 | /// Conversion from PyBool to bool. 52 | pub fn to_bool(self) -> bool { 53 | match self.val { 54 | 0 => false, 55 | _ => true, 56 | } 57 | } 58 | 59 | /// Returns PyBool as a raw pointer. Use this whenever you want to return 60 | /// a PyBool to Python. 61 | pub fn into_raw(self) -> *mut PyBool { 62 | Box::into_raw(Box::new(self)) 63 | } 64 | 65 | /// Sets value of the underlying bool 66 | pub fn load(&mut self, v: bool) { 67 | if v { 68 | self.val = 1 69 | } else { 70 | self.val = 0 71 | } 72 | } 73 | } 74 | 75 | #[doc(hidden)] 76 | #[no_mangle] 77 | pub unsafe extern "C" fn pybool_free(ptr: *mut PyBool) { 78 | if ptr.is_null() { 79 | return; 80 | } 81 | Box::from_raw(ptr); 82 | } 83 | 84 | #[doc(hidden)] 85 | #[no_mangle] 86 | pub extern "C" fn pybool_new(val: c_char) -> *mut PyBool { 87 | let val = match val { 88 | 0 => 0, 89 | _ => 1, 90 | }; 91 | let pystr = PyBool { val }; 92 | pystr.into_raw() 93 | } 94 | 95 | #[doc(hidden)] 96 | #[no_mangle] 97 | pub unsafe extern "C" fn pybool_get_val(ptr: *mut PyBool) -> i8 { 98 | let pybool = &*ptr; 99 | pybool.val 100 | } 101 | 102 | impl From for bool { 103 | fn from(b: PyBool) -> bool { 104 | b.to_bool() 105 | } 106 | } 107 | 108 | impl From for PyBool { 109 | fn from(b: bool) -> PyBool { 110 | let val = if b { 1 } else { 0 }; 111 | PyBool { val } 112 | } 113 | } 114 | 115 | impl<'a> From<&'a bool> for PyBool { 116 | fn from(b: &'a bool) -> PyBool { 117 | let val = if *b { 1 } else { 0 }; 118 | PyBool { val } 119 | } 120 | } 121 | 122 | impl From for PyBool { 123 | fn from(b: i8) -> PyBool { 124 | let val = match b { 125 | 0 => 0, 126 | _ => 1, 127 | }; 128 | PyBool { val } 129 | } 130 | } 131 | 132 | impl PartialEq for PyBool { 133 | fn eq(&self, other: &bool) -> bool { 134 | (self.val == 0 && !(*other)) || (self.val == 1 && *other) 135 | } 136 | } 137 | 138 | impl<'a> PartialEq for &'a PyBool { 139 | fn eq(&self, other: &bool) -> bool { 140 | (self.val == 0 && !(*other)) || (self.val == 1 && *other) 141 | } 142 | } 143 | 144 | impl Not for PyBool { 145 | type Output = bool; 146 | fn not(self) -> bool { 147 | match self.val { 148 | 0 => false, 149 | _ => true, 150 | } 151 | } 152 | } 153 | 154 | impl BitAnd for PyBool { 155 | type Output = bool; 156 | fn bitand(self, rhs: bool) -> bool { 157 | let val = match self.val { 158 | 0 => false, 159 | _ => true, 160 | }; 161 | val & rhs 162 | } 163 | } 164 | 165 | impl<'a> BitAnd for &'a PyBool { 166 | type Output = bool; 167 | fn bitand(self, rhs: bool) -> bool { 168 | let val = match self.val { 169 | 0 => false, 170 | _ => true, 171 | }; 172 | val & rhs 173 | } 174 | } 175 | 176 | impl<'a> BitAnd<&'a bool> for PyBool { 177 | type Output = bool; 178 | fn bitand(self, rhs: &'a bool) -> bool { 179 | let val = match self.val { 180 | 0 => false, 181 | _ => true, 182 | }; 183 | val & rhs 184 | } 185 | } 186 | 187 | impl<'a, 'b> BitAnd<&'a bool> for &'b PyBool { 188 | type Output = bool; 189 | fn bitand(self, rhs: &'a bool) -> bool { 190 | let val = match self.val { 191 | 0 => false, 192 | _ => true, 193 | }; 194 | val & rhs 195 | } 196 | } 197 | 198 | impl BitOr for PyBool { 199 | type Output = bool; 200 | fn bitor(self, rhs: bool) -> bool { 201 | let val = match self.val { 202 | 0 => false, 203 | _ => true, 204 | }; 205 | val | rhs 206 | } 207 | } 208 | 209 | impl<'a> BitOr for &'a PyBool { 210 | type Output = bool; 211 | fn bitor(self, rhs: bool) -> bool { 212 | let val = match self.val { 213 | 0 => false, 214 | _ => true, 215 | }; 216 | val | rhs 217 | } 218 | } 219 | 220 | impl<'a> BitOr<&'a bool> for PyBool { 221 | type Output = bool; 222 | fn bitor(self, rhs: &'a bool) -> bool { 223 | let val = match self.val { 224 | 0 => false, 225 | _ => true, 226 | }; 227 | val | rhs 228 | } 229 | } 230 | 231 | impl<'a, 'b> BitOr<&'a bool> for &'b PyBool { 232 | type Output = bool; 233 | fn bitor(self, rhs: &'a bool) -> bool { 234 | let val = match self.val { 235 | 0 => false, 236 | _ => true, 237 | }; 238 | val | rhs 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/librustypy/pytypes/pydict.rs: -------------------------------------------------------------------------------- 1 | //! An analog of a Python dict which contains pairs of (key, values) each of a single type, 2 | //! will accept an undefined number of one (and just one) of any other supported type 3 | //! (including other PyDict) as value and a corresponding key of a single supported hashable 4 | //! type (check [PyDictK](../pydict/enum.PyDictK.html) to see which ones are supported). 5 | //! 6 | //! PyDict can be constructed from other iterable types as long as the inner type is 7 | //! supported (a copy will be performed in case is necessary). 8 | //! 9 | //! ``` 10 | //! # use rustypy::PyDict; 11 | //! # use std::collections::HashMap; 12 | //! use std::iter::FromIterator; 13 | //! let mut hm = HashMap::from_iter(vec![(0u32, "Hi"), (1, "Rust")]); 14 | //! PyDict::from_iter(hm.clone().into_iter()); 15 | //! PyDict::from(hm); 16 | //! ``` 17 | //! 18 | //! You can also use the typical hashmap interfaces (insert, get, remove, etc.) as long as the 19 | //! type is supported (check [PyArg](../rustypy/pytypes/enum.PyArg.html) variants). PyDict 20 | //! types and their content can be converted back to a HashMap or into a (K, V) iterator. 21 | //! 22 | //! ``` 23 | //! # use rustypy::{PyDict, PyArg}; 24 | //! # use std::collections::HashMap; 25 | //! use std::iter::FromIterator; 26 | //! let hm = HashMap::from_iter(vec![(0u32, "Hello"), (1, "from"), (3, "Rust")]); 27 | //! 28 | //! let ptr = PyDict::from(hm).into_raw(); 29 | //! // to get a PyDict from a raw pointer we need to provide the key type: 30 | //! let d = unsafe { PyDict::::from_ptr(ptr) }; 31 | //! let hm_from_pd = PyDict::into_hashmap::(d.clone()); 32 | //! assert_eq!(hm_from_pd.get(&0).unwrap(), "Hello"); 33 | //! 34 | //! let hm_from_iter: HashMap = HashMap::from_iter(d.speciallized_iter::()); 35 | //! assert_eq!(hm_from_pd.get(&3).unwrap(), "Rust"); 36 | //! ``` 37 | //! 38 | //! When extracting in Python with the FFI, elements are moved, not copied 39 | //! and when free'd all the original elements are dropped. 40 | //! 41 | //! # Safety 42 | //! PyList must be passed between Rust and Python as a ```size_t``` raw pointer. You can get a 43 | //! raw pointer using ```into_raw``` and convert from a raw pointer using the "static" 44 | //! method ```PyDict::from_ptr``` which is unsafe as it requires dereferencing a raw pointer. 45 | //! PyDict also require providing the key type, in case the key type is not the expected one 46 | //! undefined behaviour will happen. 47 | //! 48 | //! For convinience there are some methods to perform conversions to ```HashMap``` 49 | //! from ```PyDict```, while none of those are unsafe per se, 50 | //! they require providing the expected PyArg enum variant. 51 | //! In case the expected variant is wrong, the process will abort and exit as it's not possible 52 | //! to handle errors acrosss the FFI boundary. 53 | //! 54 | //! ## Unpacking PyDict from Python 55 | //! Is recommended to use the [unpack_pydict!](../../macro.unpack_pydict!.html) macro in order 56 | //! to convert a PyDict to a Rust native type. Check the macro documentation for more info. 57 | 58 | use super::{abort_and_exit, PyArg, PyBool, PyList, PyString, PyTuple}; 59 | use libc::size_t; 60 | 61 | use std::collections::hash_map::Drain; 62 | use std::collections::HashMap; 63 | use std::convert::AsRef; 64 | use std::hash::Hash; 65 | use std::iter::FromIterator; 66 | use std::marker::PhantomData; 67 | use std::mem; 68 | use std::ptr; 69 | 70 | #[derive(Clone, Debug, PartialEq, Default)] 71 | pub struct PyDict 72 | where 73 | K: Eq + Hash + PyDictKey, 74 | { 75 | _inner: HashMap, 76 | } 77 | 78 | pub(crate) use self::key_bound::PyDictKey; 79 | 80 | impl PyDict 81 | where 82 | K: Eq + Hash + PyDictKey, 83 | { 84 | /// Creates an empty PyDict. 85 | pub fn new() -> PyDict { 86 | PyDict { 87 | _inner: HashMap::new(), 88 | } 89 | } 90 | 91 | /// Inserts a key-value pair into the map. 92 | /// 93 | /// If the map did not have this key present, None is returned. 94 | /// 95 | /// If the map did have this key present, the value is updated, and the old value is returned. 96 | /// The key is not updated, though; this matters for types that can be == without being 97 | /// identical. See the module-level documentation for more. 98 | pub fn insert(&mut self, k: K, v: V) -> Option 99 | where 100 | PyArg: From, 101 | V: From, 102 | { 103 | if let Some(val) = self._inner.insert(k, PyArg::from(v)) { 104 | Some(V::from(val)) 105 | } else { 106 | None 107 | } 108 | } 109 | 110 | /// Removes a key from the map, returning the value at the key if the key was previously 111 | /// in the map. 112 | /// 113 | /// The key may be any borrowed form of the map's key type, but Hash and Eq on the borrowed 114 | /// form must match those for the key type. 115 | pub fn remove(&mut self, k: &K) -> Option 116 | where 117 | V: From, 118 | { 119 | if let Some(val) = self._inner.remove(k) { 120 | Some(V::from(val)) 121 | } else { 122 | None 123 | } 124 | } 125 | 126 | /// Returns a reference to the value corresponding to the key. 127 | /// 128 | /// The key may be any borrowed form of the map's key type, but Hash and Eq on the borrowed 129 | /// form must match those for the key type. 130 | pub fn get<'a, V>(&'a self, k: &K) -> Option<&'a V> 131 | where 132 | PyArg: AsRef, 133 | { 134 | if let Some(rval) = self._inner.get(k) { 135 | Some(rval.as_ref()) 136 | } else { 137 | None 138 | } 139 | } 140 | 141 | fn get_mut_pyarg(&mut self, k: &K) -> Option<&mut PyArg> { 142 | self._inner.get_mut(k) 143 | } 144 | 145 | /// Clears the map, returning all key-value pairs as an iterator. 146 | /// Keeps the allocated memory for reuse. 147 | #[doc(hidden)] 148 | pub fn drain(&mut self) -> Drain { 149 | self._inner.drain() 150 | } 151 | 152 | /// Get a PyDict from a previously boxed PyDict. 153 | /// 154 | /// Takes the key as type parameter `K`, the raw pointer to the dictionary as argument 155 | /// and returns a PyDict with key type `K` (check 156 | /// [PyArg](../rustypy/pytypes/pydict/enum.PyDictK.html) variants for allowed key types). 157 | /// 158 | /// ``` 159 | /// # use std::collections::HashMap; 160 | /// # use rustypy::{PyDict, PyArg}; 161 | /// # use std::iter::FromIterator; 162 | /// # let hm = HashMap::from_iter(vec![(0_u64, "some"), (1, "string")]); 163 | /// # let dict = PyDict::from(hm).into_raw(); 164 | /// let dict = unsafe { PyDict::::from_ptr(dict) }; 165 | /// ``` 166 | /// 167 | pub unsafe fn from_ptr(ptr: *mut size_t) -> PyDict { 168 | *(Box::from_raw(ptr as *mut PyDict)) 169 | } 170 | 171 | /// Returns self as raw pointer. Use this method when returning a PyDict to Python. 172 | pub fn into_raw(self) -> *mut size_t { 173 | Box::into_raw(Box::new(self)) as *mut size_t 174 | } 175 | 176 | /// Consumes self and returns a HashMap, takes one type parameter and transforms inner 177 | /// content to that type. 178 | pub fn into_hashmap(mut self) -> HashMap 179 | where 180 | V: From, 181 | { 182 | HashMap::from_iter(self._inner.drain().map(|(k, v)| (k, ::from(v)))) 183 | } 184 | 185 | /// Consume self and turn it into an iterator. 186 | pub fn speciallized_iter>(self) -> IntoIter { 187 | IntoIter { 188 | inner: self._inner.into_iter(), 189 | target_t: PhantomData, 190 | } 191 | } 192 | } 193 | 194 | impl FromIterator<(K, V)> for PyDict 195 | where 196 | K: PyDictKey + Eq + Hash, 197 | PyArg: From, 198 | { 199 | fn from_iter>(iter: I) -> Self { 200 | let mut hm = HashMap::new(); 201 | for (k, v) in iter.into_iter() { 202 | hm.insert(k, PyArg::from(v)); 203 | } 204 | PyDict { _inner: hm } 205 | } 206 | } 207 | 208 | impl From> for PyDict 209 | where 210 | K: PyDictKey + Eq + Hash, 211 | PyArg: From, 212 | { 213 | fn from(mut hm: HashMap) -> PyDict { 214 | PyDict { 215 | _inner: hm 216 | .drain() 217 | .map(|(k, v)| (k, PyArg::from(v))) 218 | .collect::>(), 219 | } 220 | } 221 | } 222 | 223 | pub struct IntoIter { 224 | target_t: PhantomData, 225 | inner: ::std::collections::hash_map::IntoIter, 226 | } 227 | 228 | impl Iterator for IntoIter 229 | where 230 | V: From, 231 | K: PyDictKey + Eq + Hash, 232 | { 233 | type Item = (K, V); 234 | fn next(&mut self) -> Option<(K, V)> { 235 | match self.inner.next() { 236 | Some((k, v)) => Some((k, ::from(v))), 237 | None => None, 238 | } 239 | } 240 | fn collect(self) -> B 241 | where 242 | B: FromIterator, 243 | { 244 | self.inner.map(|(k, v)| (k, ::from(v))).collect::() 245 | } 246 | } 247 | 248 | #[doc(hidden)] 249 | #[no_mangle] 250 | pub extern "C" fn pydict_new(k_type: &PyDictK) -> *mut size_t { 251 | match *(k_type) { 252 | PyDictK::I8 => { 253 | let d: PyDict = PyDict::new(); 254 | d.into_raw() as *mut size_t 255 | } 256 | PyDictK::I16 => { 257 | let d: PyDict = PyDict::new(); 258 | d.into_raw() as *mut size_t 259 | } 260 | PyDictK::I32 => { 261 | let d: PyDict = PyDict::new(); 262 | d.into_raw() as *mut size_t 263 | } 264 | PyDictK::I64 => { 265 | let d: PyDict = PyDict::new(); 266 | d.into_raw() as *mut size_t 267 | } 268 | PyDictK::U8 => { 269 | let d: PyDict = PyDict::new(); 270 | d.into_raw() as *mut size_t 271 | } 272 | PyDictK::U16 => { 273 | let d: PyDict = PyDict::new(); 274 | d.into_raw() as *mut size_t 275 | } 276 | PyDictK::U32 => { 277 | let d: PyDict = PyDict::new(); 278 | d.into_raw() as *mut size_t 279 | } 280 | PyDictK::U64 => { 281 | let d: PyDict = PyDict::new(); 282 | d.into_raw() as *mut size_t 283 | } 284 | PyDictK::PyString => { 285 | let d: PyDict = PyDict::new(); 286 | d.into_raw() as *mut size_t 287 | } 288 | PyDictK::PyBool => { 289 | let d: PyDict = PyDict::new(); 290 | d.into_raw() as *mut size_t 291 | } 292 | } 293 | } 294 | 295 | #[doc(hidden)] 296 | #[no_mangle] 297 | pub unsafe extern "C" fn pydict_insert( 298 | dict: *mut size_t, 299 | k_type: &PyDictK, 300 | key: *mut PyArg, 301 | value: *mut PyArg, 302 | ) { 303 | macro_rules! _match_pyarg_in { 304 | ($p:ident; $v:tt) => {{ 305 | match *(Box::from_raw($p as *mut PyArg)) { 306 | PyArg::$v(val) => val, 307 | _ => abort_and_exit( 308 | "expected different key type \ 309 | for PyDict while inserting a (key, val) pair", 310 | ), 311 | } 312 | }}; 313 | } 314 | match *(k_type) { 315 | PyDictK::I8 => { 316 | let dict = &mut *(dict as *mut PyDict); 317 | let key = _match_pyarg_in!(key; I8); 318 | let value = *(Box::from_raw(value)); 319 | dict.insert(key, value); 320 | } 321 | PyDictK::I16 => { 322 | let dict = &mut *(dict as *mut PyDict); 323 | let key = _match_pyarg_in!(key; I16); 324 | let value = *(Box::from_raw(value)); 325 | dict.insert(key, value); 326 | } 327 | PyDictK::I32 => { 328 | let dict = &mut *(dict as *mut PyDict); 329 | let key = _match_pyarg_in!(key; I32); 330 | let value = *(Box::from_raw(value)); 331 | dict.insert(key, value); 332 | } 333 | PyDictK::I64 => { 334 | let dict = &mut *(dict as *mut PyDict); 335 | let key = _match_pyarg_in!(key; I64); 336 | let value = *(Box::from_raw(value)); 337 | dict.insert(key, value); 338 | } 339 | PyDictK::U8 => { 340 | let dict = &mut *(dict as *mut PyDict); 341 | let key = _match_pyarg_in!(key; U8); 342 | let value = *(Box::from_raw(value)); 343 | dict.insert(key, value); 344 | } 345 | PyDictK::U16 => { 346 | let dict = &mut *(dict as *mut PyDict); 347 | let key = _match_pyarg_in!(key; U16); 348 | let value = *(Box::from_raw(value)); 349 | dict.insert(key, value); 350 | } 351 | PyDictK::U32 => { 352 | let dict = &mut *(dict as *mut PyDict); 353 | let key = _match_pyarg_in!(key; U32); 354 | let value = *(Box::from_raw(value)); 355 | dict.insert(key, value); 356 | } 357 | PyDictK::U64 => { 358 | let dict = &mut *(dict as *mut PyDict); 359 | let key = _match_pyarg_in!(key; U64); 360 | let value = *(Box::from_raw(value)); 361 | dict.insert(key, value); 362 | } 363 | PyDictK::PyString => { 364 | let dict = &mut *(dict as *mut PyDict); 365 | let key = _match_pyarg_in!(key; PyString); 366 | let value = *(Box::from_raw(value)); 367 | dict.insert(key, value); 368 | } 369 | PyDictK::PyBool => { 370 | let dict = &mut *(dict as *mut PyDict); 371 | let key = _match_pyarg_in!(key; PyBool); 372 | let value = *(Box::from_raw(value)); 373 | dict.insert(key, value); 374 | } 375 | }; 376 | } 377 | 378 | #[test] 379 | fn drain_dict() { 380 | unsafe { 381 | let match_kv = |kv: *mut PyDictPair| match *Box::from_raw(kv as *mut PyDictPair) { 382 | PyDictPair { 383 | key: PyArg::U16(0), 384 | val: PyArg::PyString(val), 385 | } => { 386 | assert_eq!(val, PyString::from("zero")); 387 | } 388 | PyDictPair { 389 | key: PyArg::U16(1), 390 | val: PyArg::PyString(val), 391 | } => { 392 | assert_eq!(val, PyString::from("one")); 393 | } 394 | _ => panic!(), 395 | }; 396 | 397 | let mut hm = HashMap::new(); 398 | hm.insert(0u16, PyArg::PyString(PyString::from("zero"))); 399 | hm.insert(1u16, PyArg::PyString(PyString::from("one"))); 400 | let dict = PyDict::from_iter(hm).into_raw() as *mut size_t; 401 | 402 | let k_type = PyDictK::U16; 403 | let iter = pydict_get_drain(dict, &k_type); 404 | 405 | let e0 = pydict_drain_element(iter, &k_type); 406 | assert!(!e0.is_null()); 407 | match_kv(e0); 408 | 409 | let e1 = pydict_drain_element(iter, &k_type); 410 | assert!(!e1.is_null()); 411 | match_kv(e1); 412 | 413 | let none = pydict_drain_element(iter, &k_type); 414 | assert!(none.is_null()); 415 | } 416 | } 417 | 418 | #[doc(hidden)] 419 | #[no_mangle] 420 | pub unsafe extern "C" fn pydict_get_drain(dict: *mut size_t, k_type: &PyDictK) -> *mut size_t { 421 | match *(k_type) { 422 | PyDictK::I8 => { 423 | let dict = &mut *(dict as *mut PyDict); 424 | Box::into_raw(Box::new(dict.drain())) as *mut size_t 425 | } 426 | PyDictK::I16 => { 427 | let dict = &mut *(dict as *mut PyDict); 428 | Box::into_raw(Box::new(dict.drain())) as *mut size_t 429 | } 430 | PyDictK::I32 => { 431 | let dict = &mut *(dict as *mut PyDict); 432 | Box::into_raw(Box::new(dict.drain())) as *mut size_t 433 | } 434 | PyDictK::I64 => { 435 | let dict = &mut *(dict as *mut PyDict); 436 | Box::into_raw(Box::new(dict.drain())) as *mut size_t 437 | } 438 | PyDictK::U8 => { 439 | let dict = &mut *(dict as *mut PyDict); 440 | Box::into_raw(Box::new(dict.drain())) as *mut size_t 441 | } 442 | PyDictK::U16 => { 443 | let dict = &mut *(dict as *mut PyDict); 444 | Box::into_raw(Box::new(dict.drain())) as *mut size_t 445 | } 446 | PyDictK::U32 => { 447 | let dict = &mut *(dict as *mut PyDict); 448 | Box::into_raw(Box::new(dict.drain())) as *mut size_t 449 | } 450 | PyDictK::U64 => { 451 | let dict = &mut *(dict as *mut PyDict); 452 | Box::into_raw(Box::new(dict.drain())) as *mut size_t 453 | } 454 | PyDictK::PyString => { 455 | let dict = &mut *(dict as *mut PyDict); 456 | Box::into_raw(Box::new(dict.drain())) as *mut size_t 457 | } 458 | PyDictK::PyBool => { 459 | let dict = &mut *(dict as *mut PyDict); 460 | Box::into_raw(Box::new(dict.drain())) as *mut size_t 461 | } 462 | } 463 | } 464 | 465 | #[doc(hidden)] 466 | pub struct PyDictPair { 467 | key: PyArg, 468 | val: PyArg, 469 | } 470 | 471 | impl PyDictPair { 472 | fn kv_return_tuple(k: PyArg, v: PyArg) -> *mut PyDictPair { 473 | Box::into_raw(Box::new(PyDictPair { key: k, val: v })) 474 | } 475 | } 476 | 477 | #[doc(hidden)] 478 | #[no_mangle] 479 | pub unsafe extern "C" fn pydict_get_kv(a: i32, pair: *mut PyDictPair) -> *mut PyArg { 480 | let pair = &mut *(pair); 481 | match a { 482 | 0 => { 483 | let k = mem::replace(&mut pair.key, PyArg::None); 484 | Box::into_raw(Box::new(k)) 485 | } 486 | 1 => { 487 | let v = mem::replace(&mut pair.val, PyArg::None); 488 | Box::into_raw(Box::new(v)) 489 | } 490 | _ => panic!(), 491 | } 492 | } 493 | 494 | #[doc(hidden)] 495 | #[no_mangle] 496 | pub unsafe extern "C" fn pydict_free_kv(pair: *mut PyDictPair) { 497 | Box::from_raw(pair); 498 | } 499 | 500 | #[doc(hidden)] 501 | #[no_mangle] 502 | pub unsafe extern "C" fn pydict_drain_element( 503 | iter: *mut size_t, 504 | k_type: &PyDictK, 505 | ) -> *mut PyDictPair { 506 | fn _get_null() -> *mut PyDictPair { 507 | let p: *mut PyDictPair = ptr::null_mut(); 508 | p 509 | } 510 | match *(k_type) { 511 | PyDictK::I8 => { 512 | let iter = &mut *(iter as *mut Drain); 513 | match iter.next() { 514 | Some(val) => PyDictPair::kv_return_tuple(PyArg::I8(val.0), val.1), 515 | None => _get_null(), 516 | } 517 | } 518 | PyDictK::I16 => { 519 | let iter = &mut *(iter as *mut Drain); 520 | match iter.next() { 521 | Some(val) => PyDictPair::kv_return_tuple(PyArg::I16(val.0), val.1), 522 | None => _get_null(), 523 | } 524 | } 525 | PyDictK::I32 => { 526 | let iter = &mut *(iter as *mut Drain); 527 | match iter.next() { 528 | Some(val) => PyDictPair::kv_return_tuple(PyArg::I32(val.0), val.1), 529 | None => _get_null(), 530 | } 531 | } 532 | PyDictK::I64 => { 533 | let iter = &mut *(iter as *mut Drain); 534 | match iter.next() { 535 | Some(val) => PyDictPair::kv_return_tuple(PyArg::I64(val.0), val.1), 536 | None => _get_null(), 537 | } 538 | } 539 | PyDictK::U8 => { 540 | let iter = &mut *(iter as *mut Drain); 541 | match iter.next() { 542 | Some(val) => PyDictPair::kv_return_tuple(PyArg::U8(val.0), val.1), 543 | None => _get_null(), 544 | } 545 | } 546 | PyDictK::U16 => { 547 | let iter = &mut *(iter as *mut Drain); 548 | match iter.next() { 549 | Some(val) => PyDictPair::kv_return_tuple(PyArg::U16(val.0), val.1), 550 | None => _get_null(), 551 | } 552 | } 553 | PyDictK::U32 => { 554 | let iter = &mut *(iter as *mut Drain); 555 | match iter.next() { 556 | Some(val) => PyDictPair::kv_return_tuple(PyArg::U32(val.0), val.1), 557 | None => _get_null(), 558 | } 559 | } 560 | PyDictK::U64 => { 561 | let iter = &mut *(iter as *mut Drain); 562 | match iter.next() { 563 | Some(val) => PyDictPair::kv_return_tuple(PyArg::U64(val.0), val.1), 564 | None => _get_null(), 565 | } 566 | } 567 | PyDictK::PyString => { 568 | let iter = &mut *(iter as *mut Drain); 569 | match iter.next() { 570 | Some(val) => PyDictPair::kv_return_tuple(PyArg::PyString(val.0), val.1), 571 | None => _get_null(), 572 | } 573 | } 574 | PyDictK::PyBool => { 575 | let iter = &mut *(iter as *mut Drain); 576 | match iter.next() { 577 | Some(val) => PyDictPair::kv_return_tuple(PyArg::PyBool(val.0), val.1), 578 | None => _get_null(), 579 | } 580 | } 581 | } 582 | } 583 | 584 | #[doc(hidden)] 585 | #[no_mangle] 586 | pub unsafe extern "C" fn pydict_get_mut_element( 587 | dict: *mut size_t, 588 | k_type: &PyDictK, 589 | key: *mut size_t, 590 | ) -> *mut size_t { 591 | macro_rules! _match_pyarg_out { 592 | ($p:ident) => {{ 593 | match *$p { 594 | PyArg::I64(ref mut val) => val as *mut i64 as *mut size_t, 595 | PyArg::I32(ref mut val) => val as *mut i32 as *mut size_t, 596 | PyArg::I16(ref mut val) => val as *mut i16 as *mut size_t, 597 | PyArg::I8(ref mut val) => val as *mut i8 as *mut size_t, 598 | PyArg::U32(ref mut val) => val as *mut u32 as *mut size_t, 599 | PyArg::U16(ref mut val) => val as *mut u16 as *mut size_t, 600 | PyArg::U8(ref mut val) => val as *mut u8 as *mut size_t, 601 | PyArg::F32(ref mut val) => val as *mut f32 as *mut size_t, 602 | PyArg::F64(ref mut val) => val as *mut f64 as *mut size_t, 603 | PyArg::PyBool(ref mut val) => val as *mut PyBool as *mut size_t, 604 | PyArg::PyString(ref mut val) => val as *mut PyString as *mut size_t, 605 | PyArg::PyTuple(ref mut val) => &mut **val as *mut PyTuple as *mut size_t, 606 | PyArg::PyList(ref mut val) => &mut **val as *mut PyList as *mut size_t, 607 | PyArg::PyDict(rval) => rval, 608 | _ => _get_null() as *mut size_t, 609 | } 610 | }}; 611 | } 612 | fn _get_null() -> *mut PyArg { 613 | let p: *mut PyArg = ptr::null_mut(); 614 | p 615 | }; 616 | match *(k_type) { 617 | PyDictK::I8 => { 618 | let dict = &mut *(dict as *mut PyDict); 619 | let key = *(Box::from_raw(key as *mut i8)); 620 | match dict.get_mut_pyarg(&key) { 621 | Some(val) => _match_pyarg_out!(val), 622 | None => _get_null() as *mut size_t, 623 | } 624 | } 625 | PyDictK::I16 => { 626 | let dict = &mut *(dict as *mut PyDict); 627 | let key = *(Box::from_raw(key as *mut i16)); 628 | match dict.get_mut_pyarg(&key) { 629 | Some(val) => _match_pyarg_out!(val), 630 | None => _get_null() as *mut size_t, 631 | } 632 | } 633 | PyDictK::I32 => { 634 | let dict = &mut *(dict as *mut PyDict); 635 | let key = *(Box::from_raw(key as *mut i32)); 636 | match dict.get_mut_pyarg(&key) { 637 | Some(val) => _match_pyarg_out!(val), 638 | None => _get_null() as *mut size_t, 639 | } 640 | } 641 | PyDictK::I64 => { 642 | let dict = &mut *(dict as *mut PyDict); 643 | let key = *(Box::from_raw(key as *mut i64)); 644 | match dict.get_mut_pyarg(&key) { 645 | Some(val) => _match_pyarg_out!(val), 646 | None => _get_null() as *mut size_t, 647 | } 648 | } 649 | PyDictK::U8 => { 650 | let dict = &mut *(dict as *mut PyDict); 651 | let key = *(Box::from_raw(key as *mut u8)); 652 | match dict.get_mut_pyarg(&key) { 653 | Some(val) => _match_pyarg_out!(val), 654 | None => _get_null() as *mut size_t, 655 | } 656 | } 657 | PyDictK::U16 => { 658 | let dict = &mut *(dict as *mut PyDict); 659 | let key = *(Box::from_raw(key as *mut u16)); 660 | match dict.get_mut_pyarg(&key) { 661 | Some(val) => _match_pyarg_out!(val), 662 | None => _get_null() as *mut size_t, 663 | } 664 | } 665 | PyDictK::U32 => { 666 | let dict = &mut *(dict as *mut PyDict); 667 | let key = *(Box::from_raw(key as *mut u32)); 668 | match dict.get_mut_pyarg(&key) { 669 | Some(val) => _match_pyarg_out!(val), 670 | None => _get_null() as *mut size_t, 671 | } 672 | } 673 | PyDictK::U64 => { 674 | let dict = &mut *(dict as *mut PyDict); 675 | let key = *(Box::from_raw(key as *mut u64)); 676 | match dict.get_mut_pyarg(&key) { 677 | Some(val) => _match_pyarg_out!(val), 678 | None => _get_null() as *mut size_t, 679 | } 680 | } 681 | PyDictK::PyString => { 682 | let dict = &mut *(dict as *mut PyDict); 683 | let key = *(Box::from_raw(key as *mut PyString)); 684 | match dict.get_mut_pyarg(&key) { 685 | Some(val) => _match_pyarg_out!(val), 686 | None => _get_null() as *mut size_t, 687 | } 688 | } 689 | PyDictK::PyBool => { 690 | let dict = &mut *(dict as *mut PyDict); 691 | let key = *(Box::from_raw(key as *mut PyBool)); 692 | match dict.get_mut_pyarg(&key) { 693 | Some(val) => _match_pyarg_out!(val), 694 | None => _get_null() as *mut size_t, 695 | } 696 | } 697 | } 698 | } 699 | 700 | #[doc(hidden)] 701 | #[no_mangle] 702 | pub unsafe extern "C" fn pydict_free(dict: *mut size_t, k_type: &PyDictK) { 703 | if dict.is_null() { 704 | return; 705 | } 706 | match *(k_type) { 707 | PyDictK::I8 => { 708 | Box::from_raw(dict as *mut PyDict); 709 | } 710 | PyDictK::I16 => { 711 | Box::from_raw(dict as *mut PyDict); 712 | } 713 | PyDictK::I32 => { 714 | Box::from_raw(dict as *mut PyDict); 715 | } 716 | PyDictK::I64 => { 717 | Box::from_raw(dict as *mut PyDict); 718 | } 719 | PyDictK::U8 => { 720 | Box::from_raw(dict as *mut PyDict); 721 | } 722 | PyDictK::U16 => { 723 | Box::from_raw(dict as *mut PyDict); 724 | } 725 | PyDictK::U32 => { 726 | Box::from_raw(dict as *mut PyDict); 727 | } 728 | PyDictK::U64 => { 729 | Box::from_raw(dict as *mut PyDict); 730 | } 731 | PyDictK::PyString => { 732 | Box::from_raw(dict as *mut PyDict); 733 | } 734 | PyDictK::PyBool => { 735 | Box::from_raw(dict as *mut PyDict); 736 | } 737 | } 738 | } 739 | 740 | /// Types allowed as PyDict key values. 741 | pub enum PyDictK { 742 | I64, 743 | I32, 744 | I16, 745 | I8, 746 | U64, 747 | U32, 748 | U16, 749 | U8, 750 | PyBool, 751 | PyString, 752 | } 753 | 754 | pub(crate) mod key_bound { 755 | use crate::pytypes::pybool::PyBool; 756 | use crate::pytypes::pystring::PyString; 757 | 758 | pub trait PyDictKey {} 759 | impl PyDictKey for i64 {} 760 | impl PyDictKey for i32 {} 761 | impl PyDictKey for i16 {} 762 | impl PyDictKey for i8 {} 763 | impl PyDictKey for u64 {} 764 | impl PyDictKey for u32 {} 765 | impl PyDictKey for u16 {} 766 | impl PyDictKey for u8 {} 767 | impl PyDictKey for PyString {} 768 | impl PyDictKey for PyBool {} 769 | } 770 | 771 | #[doc(hidden)] 772 | #[no_mangle] 773 | pub extern "C" fn pydict_get_key_type(k: u32) -> *mut PyDictK { 774 | match k { 775 | 1 => Box::into_raw(Box::new(PyDictK::U8)), 776 | 2 => Box::into_raw(Box::new(PyDictK::I8)), 777 | 3 => Box::into_raw(Box::new(PyDictK::I16)), 778 | 4 => Box::into_raw(Box::new(PyDictK::U16)), 779 | 5 => Box::into_raw(Box::new(PyDictK::I32)), 780 | 6 => Box::into_raw(Box::new(PyDictK::U32)), 781 | 7 => Box::into_raw(Box::new(PyDictK::I64)), 782 | 8 => Box::into_raw(Box::new(PyDictK::U64)), 783 | 11 => Box::into_raw(Box::new(PyDictK::PyBool)), 784 | 12 => Box::into_raw(Box::new(PyDictK::PyString)), 785 | _ => abort_and_exit("type not supported as PyDict key type"), 786 | } 787 | } 788 | -------------------------------------------------------------------------------- /src/librustypy/pytypes/pylist.rs: -------------------------------------------------------------------------------- 1 | //! An analog of a Python list which contains elements of a single type, will accept an 2 | //! undefined number of one (and just one) of any other supported type (including other 3 | //! PyLists). 4 | //! 5 | //! PyList can be constructed from other iterable types as long as the inner type is 6 | //! supported (a copy will be performed in case is necessary). 7 | //! 8 | //! ``` 9 | //! # use rustypy::PyList; 10 | //! # use std::iter::FromIterator; 11 | //! PyList::from_iter(vec![1u32; 3]); 12 | //! PyList::from(vec![1u32; 3]); 13 | //! ``` 14 | //! 15 | //! You can also use the typical vector interfaces (push, pop, remove, etc.) as long as the 16 | //! type is supported (check [PyArg](../rustypy/pytypes/enum.PyArg.html) variants). PyList 17 | //! types and their content can also be implicitly converted through an special iterator type. 18 | //! 19 | //! ``` 20 | //! # use rustypy::PyList; 21 | //! let mut l = PyList::new(); 22 | //! for e in vec![0u32, 1, 3] { 23 | //! l.push(e); 24 | //! } 25 | //! 26 | //! let mut iter = PyList::speciallized_iter::(l); 27 | //! assert_eq!(iter.collect::>(), vec![0u32, 1, 3]) 28 | //! ``` 29 | //! 30 | //! When extracting in Python with the FFI, elements are moved whenever is possible, not copied 31 | //! and when free'd all the original elements are dropped. 32 | //! 33 | //! # Safety 34 | //! PyList must be passed between Rust and Python as a raw pointer. You can get a raw pointer 35 | //! using ```into_raw``` and convert from a raw pointer using the "static" 36 | //! method ```PyList::from_ptr``` which is unsafe as it requires dereferencing a raw pointer. 37 | //! 38 | //! For convinience there are some methods to perform conversions to Vec from PyList, 39 | //! while none of those are unsafe per se, they require providing the expected PyArg enum variant. 40 | //! In case the expected variant is wrong, the process will abort and exit as it's not possible 41 | //! to handle errors acrosss the FFI boundary. 42 | //! 43 | //! ## Unpacking PyList from Python 44 | //! Is recommended to use the [unpack_pylist!](../../macro.unpack_pylist!.html) macro in order 45 | //! to convert a PyList to a Rust native type. Check the macro documentation for more info. 46 | 47 | use super::PyArg; 48 | 49 | use std::iter::{FromIterator, IntoIterator}; 50 | use std::marker::PhantomData; 51 | use std::ops::{Index, IndexMut}; 52 | 53 | /// An analog of a Python list which contains an undefined number of elements of 54 | /// a single kind, of any [supported type](../../../rustypy/pytypes/enum.PyArg.html). 55 | /// 56 | /// Read the [module docs](index.html) for more information. 57 | #[derive(Clone, Debug, PartialEq, Default)] 58 | pub struct PyList { 59 | _inner: Vec, 60 | } 61 | 62 | impl PyList { 63 | /// Constructs a new, empty ```PyList```. 64 | /// 65 | /// The vector will not allocate until elements are pushed onto it. 66 | pub fn new() -> PyList { 67 | PyList { _inner: Vec::new() } 68 | } 69 | 70 | /// Removes and returns the element at position ```index``` within the vector, 71 | /// shifting all elements after it to the left. 72 | pub fn remove(&mut self, index: usize) -> T 73 | where 74 | T: From, 75 | { 76 | T::from(self._inner.remove(index)) 77 | } 78 | 79 | /// Removes the last element from a vector and returns it, or ```None``` if it is empty. 80 | pub fn pop(&mut self) -> Option 81 | where 82 | T: From, 83 | { 84 | if let Some(val) = self._inner.pop() { 85 | Some(T::from(val)) 86 | } else { 87 | None 88 | } 89 | } 90 | 91 | /// Returns the number of elements in the PyList. 92 | pub fn len(&self) -> usize { 93 | self._inner.len() 94 | } 95 | 96 | pub fn is_empty(&self) -> bool { 97 | self._inner.is_empty() 98 | } 99 | 100 | /// Appends an element to the back of a collection. 101 | /// 102 | /// ##Panics 103 | /// 104 | /// Panics if the number of elements in the vector overflows a usize. 105 | pub fn push(&mut self, a: T) 106 | where 107 | PyArg: From, 108 | { 109 | self._inner.push(PyArg::from(a)) 110 | } 111 | 112 | /// Get a PyList from a previously boxed raw pointer. 113 | pub unsafe fn from_ptr(ptr: *mut PyList) -> PyList { 114 | *(Box::from_raw(ptr)) 115 | } 116 | 117 | /// Return a PyList as a raw pointer. 118 | pub fn into_raw(self) -> *mut PyList { 119 | Box::into_raw(Box::new(self)) 120 | } 121 | 122 | /// Consume self and turn it into an iterator. 123 | pub fn speciallized_iter>(self) -> IntoIter { 124 | IntoIter { 125 | inner: self._inner.into_iter(), 126 | target_t: PhantomData, 127 | } 128 | } 129 | } 130 | 131 | impl FromIterator for PyList 132 | where 133 | PyArg: From, 134 | { 135 | fn from_iter>(iter: I) -> Self { 136 | let mut c = PyList::new(); 137 | for e in iter { 138 | c.push(e); 139 | } 140 | c 141 | } 142 | } 143 | 144 | pub struct IntoIter { 145 | target_t: PhantomData, 146 | inner: ::std::vec::IntoIter, 147 | } 148 | 149 | impl Iterator for IntoIter 150 | where 151 | T: From, 152 | { 153 | type Item = T; 154 | fn next(&mut self) -> Option { 155 | match self.inner.next() { 156 | Some(val) => Some(::from(val)), 157 | None => None, 158 | } 159 | } 160 | fn collect(self) -> B 161 | where 162 | B: FromIterator, 163 | { 164 | self.inner.map(::from).collect::() 165 | } 166 | } 167 | 168 | impl Into> for PyList 169 | where 170 | PyArg: Into, 171 | { 172 | fn into(mut self) -> Vec { 173 | self._inner.drain(..).map(PyArg::into).collect() 174 | } 175 | } 176 | 177 | impl From> for PyList 178 | where 179 | PyArg: From, 180 | { 181 | fn from(mut v: Vec) -> PyList { 182 | PyList { 183 | _inner: v.drain(..).map(PyArg::from).collect(), 184 | } 185 | } 186 | } 187 | 188 | impl Index for PyList { 189 | type Output = PyArg; 190 | fn index(&self, index: usize) -> &PyArg { 191 | &(self._inner[index]) 192 | } 193 | } 194 | 195 | impl<'a> IndexMut for PyList { 196 | fn index_mut(&mut self, index: usize) -> &mut PyArg { 197 | &mut (self._inner[index]) 198 | } 199 | } 200 | 201 | #[doc(hidden)] 202 | #[no_mangle] 203 | pub unsafe extern "C" fn pylist_new(len: usize) -> *mut PyList { 204 | let list = PyList { 205 | _inner: Vec::with_capacity(len), 206 | }; 207 | list.into_raw() 208 | } 209 | 210 | #[doc(hidden)] 211 | #[no_mangle] 212 | pub unsafe extern "C" fn pylist_push(list: &mut PyList, e: *mut PyArg) { 213 | list.push(*(Box::from_raw(e))); 214 | } 215 | 216 | #[doc(hidden)] 217 | #[no_mangle] 218 | pub unsafe extern "C" fn pylist_len(list: &mut PyList) -> usize { 219 | list.len() 220 | } 221 | 222 | #[doc(hidden)] 223 | #[no_mangle] 224 | pub unsafe extern "C" fn pylist_free(ptr: *mut PyList) { 225 | if ptr.is_null() { 226 | return; 227 | } 228 | Box::from_raw(ptr); 229 | } 230 | 231 | #[doc(hidden)] 232 | #[no_mangle] 233 | pub unsafe extern "C" fn pylist_get_element(ptr: *mut PyList, index: usize) -> *mut PyArg { 234 | let list = &mut *ptr; 235 | Box::into_raw(Box::new(PyList::remove(list, index))) 236 | } 237 | -------------------------------------------------------------------------------- /src/librustypy/pytypes/pystring.rs: -------------------------------------------------------------------------------- 1 | //! An analog of a Python String. 2 | //! 3 | //! To return to Python you must use ```into_raw``` method and return a raw pointer. 4 | //! You can create them using the ```from``` trait method, from both ```&str``` and ```String```. 5 | //! 6 | //! # Safety 7 | //! When passed from Python you can convert from PyString to an owned string 8 | //! (```from_ptr_into_string``` method) or to a ```&str``` slice (to_str method), or 9 | //! to a PyString reference (```from_ptr``` method). Those operations are unsafe 10 | //! as they require dereferencing a raw pointer. 11 | //! 12 | //! # Examples 13 | //! 14 | //! ``` 15 | //! use rustypy::PyString; 16 | //! let pystr = PyString::from("Hello world!"); 17 | //! 18 | //! // prepare to return to Python: 19 | //! let ptr = pystr.into_raw(); 20 | //! // convert from raw pointer to an owned String 21 | //! let rust_string = unsafe { PyString::from_ptr_to_string(ptr) }; 22 | //! ``` 23 | use libc::c_char; 24 | use std::ffi::CString; 25 | 26 | use std::convert::From; 27 | use std::fmt; 28 | 29 | /// An analog of a Python string. 30 | /// 31 | /// Read the [module docs](index.html) for more information. 32 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 33 | pub struct PyString { 34 | _inner: CString, 35 | } 36 | 37 | impl PyString { 38 | /// Get a PyString from a previously boxed raw pointer. 39 | /// 40 | /// # Safety 41 | /// Ensure that the passed ptr is valid 42 | pub unsafe fn from_ptr(ptr: *mut PyString) -> PyString { 43 | if ptr.is_null() { 44 | panic!("trying to deref a null ptr!"); 45 | } 46 | *Box::from_raw(ptr) 47 | } 48 | 49 | /// Constructs an owned String from a raw pointer. 50 | /// 51 | /// # Safety 52 | /// Ensure that the passed ptr is valid 53 | pub unsafe fn from_ptr_to_string(ptr: *mut PyString) -> String { 54 | if ptr.is_null() { 55 | panic!("trying to deref a null ptr!"); 56 | } 57 | let pystr = *(Box::from_raw(ptr)); 58 | String::from(pystr._inner.to_str().unwrap()) 59 | } 60 | /// Returns PyString as a raw pointer. Use this whenever you want to return 61 | /// a PyString to Python. 62 | pub fn into_raw(self) -> *mut PyString { 63 | Box::into_raw(Box::new(self)) 64 | } 65 | 66 | /// Return a PyString from a raw char pointer. 67 | pub unsafe fn from_raw(ptr: *const c_char) -> PyString { 68 | PyString { 69 | _inner: CStr::from_ptr(ptr).to_owned(), 70 | } 71 | } 72 | } 73 | 74 | impl fmt::Display for PyString { 75 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 76 | write!(f, "{}", String::from(self._inner.to_str().unwrap())) 77 | } 78 | } 79 | 80 | impl<'a> From<&'a str> for PyString { 81 | /// Copies a string slice to a PyString. 82 | fn from(s: &'a str) -> PyString { 83 | PyString { 84 | _inner: CString::new(s).unwrap(), 85 | } 86 | } 87 | } 88 | 89 | impl From for PyString { 90 | /// Copies a String to a PyString. 91 | fn from(s: String) -> PyString { 92 | PyString { 93 | _inner: CString::new(s).unwrap(), 94 | } 95 | } 96 | } 97 | 98 | impl From for String { 99 | fn from(s: PyString) -> String { 100 | s.to_string() 101 | } 102 | } 103 | 104 | /// Destructs the PyString, mostly to be used from Python. 105 | #[doc(hidden)] 106 | #[no_mangle] 107 | pub unsafe extern "C" fn pystring_free(ptr: *mut PyString) { 108 | if ptr.is_null() { 109 | return; 110 | } 111 | Box::from_raw(ptr); 112 | } 113 | 114 | use std::ffi::CStr; 115 | /// Creates a PyString wrapper from a raw c_char pointer 116 | #[doc(hidden)] 117 | #[no_mangle] 118 | pub unsafe extern "C" fn pystring_new(ptr: *const c_char) -> *mut PyString { 119 | let pystr = PyString { 120 | _inner: CStr::from_ptr(ptr).to_owned(), 121 | }; 122 | pystr.into_raw() 123 | } 124 | 125 | /// Consumes the wrapper and returns a raw c_char pointer. Afterwards is not necessary 126 | /// to destruct it as it has already been consumed. 127 | #[doc(hidden)] 128 | #[no_mangle] 129 | pub unsafe extern "C" fn pystring_get_str(ptr: *mut PyString) -> *const c_char { 130 | let pystr: PyString = PyString::from_ptr(ptr); 131 | pystr._inner.into_raw() 132 | } 133 | 134 | #[cfg(test)] 135 | mod tests { 136 | use super::*; 137 | 138 | #[test] 139 | fn pystring_operations() { 140 | let source = "test string"; 141 | let owned_pystr = PyString::from(source).into_raw(); 142 | let back_from_py = unsafe { PyString::from_ptr_to_string(owned_pystr) }; 143 | assert_eq!(back_from_py, "test string"); 144 | { 145 | String::from(source); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/librustypy/pytypes/pytuple.rs: -------------------------------------------------------------------------------- 1 | //! An analog of a Python tuple, will accept an undefined number of any other supported types. 2 | //! 3 | //! You can construct it using the [pytuple!](../../macro.pytuple!.html) macro, ie: 4 | //! 5 | //! ``` 6 | //! # #[macro_use] extern crate rustypy; 7 | //! # fn main(){ 8 | //! use rustypy::PyArg; 9 | //! pytuple!(PyArg::I64(10), PyArg::F32(10.5)); 10 | //! # } 11 | //! ``` 12 | //! 13 | //! You must pass the variety of the argument using the PyArg enum. 14 | //! 15 | //! When extracting elements in Python with the FFI, elements are copied, not moved unless 16 | //! possible (ie. content of inner containers may or may not be moved out), 17 | //! and when free'd all the original elements are dropped. 18 | //! 19 | //! PyTuples behave exactly as Python tuples: they are immutable, but provide interior mutability. 20 | //! For example, you can pop elements from an inner PyList, although the PyList cannot be moved 21 | //! out of a PyTuple (without completely destructuring it). 22 | //! 23 | //! # Safety 24 | //! PyTuple must be passed between Rust and Python as a raw pointer. You can get a 25 | //! raw pointer using ```into_raw``` and convert from a raw pointer using the "static" 26 | //! method ```PyDict::from_ptr``` which is unsafe as it requires dereferencing a raw pointer. 27 | //! 28 | //! ## Unpacking PyTuple from Python 29 | //! Is recommended to use the [unpack_pytuple!](../../macro.unpack_pytuple!.html) macro in order 30 | //! to convert a PyTuple to a Rust native type. Check the macro documentation for more info. 31 | 32 | use std::iter::IntoIterator; 33 | use std::mem; 34 | use std::ops::Deref; 35 | 36 | use crate::pytypes::PyArg; 37 | 38 | /// An analog of a Python tuple, will accept an undefined number of other 39 | /// [supported types](../../../rustypy/pytypes/enum.PyArg.html). 40 | /// 41 | /// Read the [module docs](index.html) for more information. 42 | #[derive(Clone, Debug, PartialEq)] 43 | pub struct PyTuple { 44 | pub(crate) elem: PyArg, 45 | pub(crate) idx: usize, 46 | pub(crate) next: Option>, 47 | } 48 | 49 | #[allow(clippy::len_without_is_empty)] 50 | impl<'a> PyTuple { 51 | #[doc(hidden)] 52 | pub fn new(elem: PyArg, idx: usize, next: Option>) -> PyTuple { 53 | PyTuple { elem, idx, next } 54 | } 55 | 56 | #[doc(hidden)] 57 | pub fn set_next(&mut self, next: Option>) { 58 | self.next = next; 59 | } 60 | 61 | /// Get a PyTuple from a previously boxed raw pointer. 62 | pub unsafe fn from_ptr(ptr: *mut PyTuple) -> PyTuple { 63 | *(Box::from_raw(ptr)) 64 | } 65 | 66 | /// Get a mutable reference to an inner element of the tuple, takes as argument the position 67 | /// of the element and returns a Result. 68 | pub(crate) fn as_mut(&mut self, idx: usize) -> Result<&mut PyArg, &str> { 69 | if idx == self.idx { 70 | Ok(&mut self.elem) 71 | } else { 72 | match self.next { 73 | Some(ref mut e) => (**e).as_mut(idx), 74 | None => Err("PyTuple index out of range."), 75 | } 76 | } 77 | } 78 | 79 | #[doc(hidden)] 80 | pub fn replace_elem(&mut self, idx: usize) -> Result { 81 | if idx == self.idx { 82 | let e = mem::replace(&mut self.elem, PyArg::None); 83 | Ok(e) 84 | } else { 85 | match self.next { 86 | Some(ref mut e) => (**e).replace_elem(idx), 87 | None => Err("PyTuple index out of range."), 88 | } 89 | } 90 | } 91 | 92 | /// Get a regular reference to an inner element of the tuple, takes as argument the position 93 | /// of the element and returns a Result. 94 | pub(crate) fn as_ref(&self, idx: usize) -> Result<&PyArg, &str> { 95 | if idx == self.idx { 96 | Ok(&self.elem) 97 | } else { 98 | match self.next { 99 | Some(ref e) => (**e).as_ref(idx), 100 | None => Err("PyTuple index out of range."), 101 | } 102 | } 103 | } 104 | 105 | fn push(&mut self, next: PyTuple) { 106 | self.next = Some(Box::new(next)); 107 | } 108 | 109 | /// Returns the length of the tuple. 110 | pub fn len(&self) -> usize { 111 | match self.next { 112 | Some(ref e) => e.len(), 113 | None => self.idx + 1, 114 | } 115 | } 116 | 117 | /// Returns self as raw pointer. Use this method when returning a PyTuple to Python. 118 | pub fn into_raw(self) -> *mut PyTuple { 119 | Box::into_raw(Box::new(self)) 120 | } 121 | } 122 | 123 | impl<'a> IntoIterator for &'a PyTuple { 124 | type Item = &'a PyArg; 125 | type IntoIter = ::std::vec::IntoIter<&'a PyArg>; 126 | fn into_iter(self) -> Self::IntoIter { 127 | let l = self.len(); 128 | let mut iter = Vec::with_capacity(l); 129 | for i in 0..l { 130 | iter.push(self.as_ref(i).unwrap()); 131 | } 132 | iter.into_iter() 133 | } 134 | } 135 | 136 | impl Deref for PyTuple { 137 | type Target = PyArg; 138 | 139 | fn deref(&self) -> &PyArg { 140 | &self.elem 141 | } 142 | } 143 | 144 | /// This macro allows the construction of [PyTuple](../rustypy/pytypes/pytuple/struct.PyTuple.html) 145 | /// types. 146 | /// 147 | /// # Examples 148 | /// 149 | /// ``` 150 | /// # #[macro_use] extern crate rustypy; 151 | /// # fn main(){ 152 | /// # use rustypy::PyArg; 153 | /// pytuple!(PyArg::I64(10), PyArg::F32(10.5)); 154 | /// # } 155 | /// ``` 156 | /// 157 | #[macro_export] 158 | macro_rules! pytuple { 159 | ( $( $elem:ident ),+ ) => {{ 160 | use rustypy::PyTuple; 161 | let mut cnt; 162 | let mut tuple = Vec::new(); 163 | cnt = 0usize; 164 | $( 165 | let tuple_e = PyTuple::new( 166 | $elem, 167 | cnt, 168 | None, 169 | ); 170 | tuple.push(tuple_e); 171 | cnt += 1; 172 | )*; 173 | if cnt == tuple.len() {}; // stub to remove warning... 174 | let t_len = tuple.len() - 1; 175 | for i in 1..(t_len + 1) { 176 | let idx = t_len - i; 177 | let last = tuple.pop().unwrap(); 178 | let prev = tuple.get_mut(idx).unwrap(); 179 | prev.set_next(Some(Box::new(last))); 180 | } 181 | tuple.pop().unwrap() 182 | }}; 183 | ( $( $elem:expr ),+ ) => {{ 184 | use rustypy::PyTuple; 185 | let mut cnt; 186 | let mut tuple = Vec::new(); 187 | cnt = 0usize; 188 | $( 189 | let tuple_e = PyTuple::new( 190 | $elem, 191 | cnt, 192 | None, 193 | ); 194 | tuple.push(tuple_e); 195 | cnt += 1; 196 | )*; 197 | let t_len = cnt - 1; 198 | for i in 1..cnt { 199 | let idx = t_len - i; 200 | let last = tuple.pop().unwrap(); 201 | let prev = tuple.get_mut(idx).unwrap(); 202 | prev.set_next(Some(Box::new(last))); 203 | } 204 | tuple.pop().unwrap() 205 | }}; 206 | } 207 | 208 | #[doc(hidden)] 209 | #[no_mangle] 210 | pub unsafe extern "C" fn pytuple_new(idx: usize, elem: *mut PyArg) -> *mut PyTuple { 211 | let tuple = PyTuple { 212 | elem: *(Box::from_raw(elem)), 213 | idx, 214 | next: None, 215 | }; 216 | tuple.into_raw() 217 | } 218 | 219 | #[doc(hidden)] 220 | #[no_mangle] 221 | pub unsafe extern "C" fn pytuple_push(next: *mut PyTuple, prev: &mut PyTuple) { 222 | let next: PyTuple = *(Box::from_raw(next)); 223 | prev.push(next) 224 | } 225 | 226 | #[doc(hidden)] 227 | #[no_mangle] 228 | pub unsafe extern "C" fn pytuple_free(ptr: *mut PyTuple) { 229 | if ptr.is_null() { 230 | return; 231 | } 232 | 233 | Box::from_raw(ptr); 234 | } 235 | 236 | #[doc(hidden)] 237 | #[no_mangle] 238 | pub unsafe extern "C" fn pytuple_len(ptr: *mut PyTuple) -> usize { 239 | let tuple = &*ptr; 240 | tuple.len() 241 | } 242 | 243 | #[doc(hidden)] 244 | #[no_mangle] 245 | pub unsafe extern "C" fn pytuple_get_element(ptr: *mut PyTuple, index: usize) -> *mut PyArg { 246 | let tuple = &mut *ptr; 247 | let elem = &PyTuple::as_mut(tuple, index).unwrap(); 248 | let copied: PyArg = (*elem).clone(); 249 | Box::into_raw(Box::new(copied)) 250 | } 251 | -------------------------------------------------------------------------------- /src/rustypy/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1.17' 2 | -------------------------------------------------------------------------------- /src/rustypy/__main__.py: -------------------------------------------------------------------------------- 1 | from rustypy.scripts import cli 2 | 3 | cli() 4 | -------------------------------------------------------------------------------- /src/rustypy/rswrapper/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List 2 | 3 | from .pytypes import HashableType, PyBool, PyDict, PyList, PyString, PyTuple 4 | from .rswrapper import (Double, Float, Tuple, UnsignedLongLong, 5 | bind_rs_crate_funcs) 6 | -------------------------------------------------------------------------------- /src/rustypy/rswrapper/ffi_defs.py: -------------------------------------------------------------------------------- 1 | """FFI definitions.""" 2 | 3 | import ctypes 4 | import importlib 5 | import os 6 | import pathlib 7 | import sys 8 | from ctypes import POINTER, c_void_p 9 | 10 | c_backend = None 11 | 12 | RS_TYPE_CONVERSION = { 13 | 'c_float': 'float', 14 | 'c_double': 'double', 15 | 'c_short': 'int', 16 | 'c_int': 'int', 17 | 'c_long': 'int', 18 | 'c_longlong': 'int', 19 | 'c_ushort': 'int', 20 | 'c_uint': 'int', 21 | 'c_ulong': 'int', 22 | 'u32': 'int', 23 | 'u16': 'int', 24 | 'u8': 'int', 25 | 'i64': 'int', 26 | 'i32': 'int', 27 | 'i16': 'int', 28 | 'i8': 'int', 29 | 'f32': 'float', 30 | 'f64': 'double', 31 | 'PyTuple': 'tuple', 32 | 'PyBool': 'bool', 33 | 'PyString': 'str', 34 | 'PyList': 'list', 35 | 'usize': 'OpaquePtr', 36 | 'size_t': 'OpaquePtr', 37 | 'void': 'None', 38 | } 39 | 40 | 41 | class Raw_RS(ctypes.Structure): 42 | pass 43 | 44 | 45 | class PyArg_RS(ctypes.Structure): 46 | pass 47 | 48 | 49 | class PyString_RS(ctypes.Structure): 50 | pass 51 | 52 | 53 | class PyBool_RS(ctypes.Structure): 54 | pass 55 | 56 | 57 | class PyTuple_RS(ctypes.Structure): 58 | pass 59 | 60 | 61 | class PyList_RS(ctypes.Structure): 62 | pass 63 | 64 | 65 | # BEGIN dictionary interfaces 66 | class PyDict_RS(ctypes.Structure): 67 | pass 68 | 69 | 70 | class KeyType_RS(ctypes.Structure): 71 | pass 72 | 73 | 74 | class DrainPyDict_RS(ctypes.Structure): 75 | pass 76 | # END dictionary interfaces 77 | 78 | 79 | class KrateData_RS(ctypes.Structure): 80 | pass 81 | 82 | 83 | def config_ctypes(): 84 | # Crate parsing functions 85 | c_backend.krate_data_new.restype = POINTER(KrateData_RS) 86 | c_backend.krate_data_free.argtypes = (POINTER(KrateData_RS), ) 87 | c_backend.krate_data_free.restype = c_void_p 88 | c_backend.krate_data_len.argtypes = (POINTER(KrateData_RS), ) 89 | c_backend.krate_data_len.restype = ctypes.c_size_t 90 | c_backend.krate_data_iter.argtypes = ( 91 | POINTER(KrateData_RS), ctypes.c_size_t) 92 | c_backend.krate_data_iter.restype = POINTER(PyString_RS) 93 | c_backend.parse_src.argtypes = ( 94 | POINTER(PyString_RS), POINTER(KrateData_RS)) 95 | c_backend.parse_src.restype = POINTER(PyString_RS) 96 | 97 | # String related functions 98 | c_backend.pystring_new.argtypes = (ctypes.c_char_p, ) 99 | c_backend.pystring_new.restype = POINTER(PyString_RS) 100 | c_backend.pystring_free.argtypes = (POINTER(PyString_RS), ) 101 | c_backend.pystring_free.restype = c_void_p 102 | c_backend.pystring_get_str.argtypes = (POINTER(PyString_RS), ) 103 | c_backend.pystring_get_str.restype = ctypes.c_char_p 104 | 105 | # Bool related functions 106 | c_backend.pybool_new.argtypes = (ctypes.c_byte, ) 107 | c_backend.pybool_new.restype = POINTER(PyBool_RS) 108 | c_backend.pybool_free.argtypes = (POINTER(PyBool_RS), ) 109 | c_backend.pybool_free.restype = c_void_p 110 | c_backend.pybool_get_val.argtypes = (POINTER(PyBool_RS), ) 111 | c_backend.pybool_get_val.restype = ctypes.c_byte 112 | 113 | # Tuple related functions 114 | c_backend.pytuple_new.argtypes = (ctypes.c_size_t, ) 115 | c_backend.pytuple_new.restype = POINTER(PyTuple_RS) 116 | c_backend.pytuple_push.argtypes = ( 117 | POINTER(PyTuple_RS), POINTER(PyTuple_RS)) 118 | c_backend.pytuple_push.restype = c_void_p 119 | c_backend.pytuple_len.argtypes = (POINTER(PyTuple_RS),) 120 | c_backend.pytuple_len.restype = ctypes.c_size_t 121 | c_backend.pytuple_free.argtypes = (POINTER(PyTuple_RS), ) 122 | c_backend.pytuple_free.restype = c_void_p 123 | c_backend.pytuple_get_element.argtypes = ( 124 | POINTER(PyTuple_RS), ctypes.c_size_t) 125 | c_backend.pytuple_get_element.restype = POINTER(PyArg_RS) 126 | 127 | # List related functions 128 | c_backend.pylist_new.argtypes = (ctypes.c_size_t, ) 129 | c_backend.pylist_new.restype = POINTER(PyList_RS) 130 | c_backend.pylist_push.argtypes = (POINTER(PyList_RS), POINTER(PyArg_RS)) 131 | c_backend.pylist_push.restype = c_void_p 132 | c_backend.pylist_len.argtypes = (POINTER(PyList_RS), ) 133 | c_backend.pylist_len.restype = ctypes.c_size_t 134 | c_backend.pylist_free.argtypes = (POINTER(PyList_RS), ) 135 | c_backend.pylist_free.restype = c_void_p 136 | c_backend.pylist_get_element.argtypes = ( 137 | POINTER(PyList_RS), ctypes.c_size_t) 138 | c_backend.pylist_get_element.restype = POINTER(PyArg_RS) 139 | 140 | # Dict related functions 141 | c_backend.pydict_new.argtypes = (POINTER(KeyType_RS), ) 142 | c_backend.pydict_new.restype = POINTER(PyDict_RS) 143 | c_backend.pydict_free.argtypes = (POINTER(PyDict_RS), POINTER(KeyType_RS)) 144 | c_backend.pydict_free.restype = c_void_p 145 | c_backend.pydict_get_key_type.argtypes = (ctypes.c_uint, ) 146 | c_backend.pydict_get_key_type.restype = POINTER(KeyType_RS) 147 | c_backend.pydict_insert.argtypes = (POINTER(PyDict_RS), POINTER( 148 | KeyType_RS), POINTER(PyArg_RS), POINTER(PyArg_RS)) 149 | c_backend.pydict_insert.restype = c_void_p 150 | # c_backend.pydict_get_mut_element.restype = c_void_p 151 | c_backend.pydict_get_drain.argtypes = ( 152 | POINTER(PyDict_RS), POINTER(KeyType_RS)) 153 | c_backend.pydict_get_drain.restype = POINTER(DrainPyDict_RS) 154 | c_backend.pydict_drain_element.argtypes = ( 155 | POINTER(DrainPyDict_RS), POINTER(KeyType_RS)) 156 | c_backend.pydict_drain_element.restype = POINTER(PyArg_RS) 157 | c_backend.pydict_get_kv.argtypes = (ctypes.c_int, POINTER(PyArg_RS)) 158 | c_backend.pydict_get_kv.restype = POINTER(PyArg_RS) 159 | c_backend.pydict_free_kv.argtypes = (POINTER(PyArg_RS),) 160 | 161 | # Wrap type in PyArg enum 162 | c_backend.pyarg_from_str.argtypes = (ctypes.c_char_p,) 163 | c_backend.pyarg_from_str.restype = POINTER(PyArg_RS) 164 | c_backend.pyarg_from_int.argtypes = (ctypes.c_longlong,) 165 | c_backend.pyarg_from_int.restype = POINTER(PyArg_RS) 166 | c_backend.pyarg_from_ulonglong.argtypes = (ctypes.c_ulonglong,) 167 | c_backend.pyarg_from_ulonglong.restype = POINTER(PyArg_RS) 168 | c_backend.pyarg_from_float.argtypes = (ctypes.c_float,) 169 | c_backend.pyarg_from_float.restype = POINTER(PyArg_RS) 170 | c_backend.pyarg_from_double.argtypes = (ctypes.c_double,) 171 | c_backend.pyarg_from_double.restype = POINTER(PyArg_RS) 172 | c_backend.pyarg_from_bool.argtypes = (ctypes.c_byte,) 173 | c_backend.pyarg_from_bool.restype = POINTER(PyArg_RS) 174 | c_backend.pyarg_from_pytuple.argtypes = (POINTER(PyTuple_RS),) 175 | c_backend.pyarg_from_pytuple.restype = POINTER(PyArg_RS) 176 | c_backend.pyarg_from_pylist.argtypes = (POINTER(PyList_RS),) 177 | c_backend.pyarg_from_pylist.restype = POINTER(PyArg_RS) 178 | c_backend.pyarg_from_pydict.argtypes = (POINTER(PyDict_RS),) 179 | c_backend.pyarg_from_pydict.restype = POINTER(PyArg_RS) 180 | # Get val from enum 181 | c_backend.pyarg_extract_owned_int.argtypes = (POINTER(PyArg_RS),) 182 | c_backend.pyarg_extract_owned_int.restype = ctypes.c_longlong 183 | c_backend.pyarg_extract_owned_ulonglong.argtypes = (POINTER(PyArg_RS),) 184 | c_backend.pyarg_extract_owned_ulonglong.restype = ctypes.c_ulonglong 185 | c_backend.pyarg_extract_owned_float.argtypes = (POINTER(PyArg_RS),) 186 | c_backend.pyarg_extract_owned_float.restype = ctypes.c_float 187 | c_backend.pyarg_extract_owned_double.argtypes = (POINTER(PyArg_RS),) 188 | c_backend.pyarg_extract_owned_double.restype = ctypes.c_double 189 | c_backend.pyarg_extract_owned_bool.argtypes = (POINTER(PyArg_RS),) 190 | c_backend.pyarg_extract_owned_bool.restype = POINTER(PyBool_RS) 191 | c_backend.pyarg_extract_owned_str.argtypes = (POINTER(PyArg_RS),) 192 | c_backend.pyarg_extract_owned_str.restype = POINTER(PyString_RS) 193 | c_backend.pyarg_extract_owned_tuple.argtypes = (POINTER(PyArg_RS),) 194 | c_backend.pyarg_extract_owned_tuple.restype = POINTER(PyTuple_RS) 195 | c_backend.pyarg_extract_owned_list.argtypes = (POINTER(PyArg_RS),) 196 | c_backend.pyarg_extract_owned_list.restype = POINTER(PyList_RS) 197 | c_backend.pyarg_extract_owned_dict.argtypes = (POINTER(PyArg_RS),) 198 | c_backend.pyarg_extract_owned_dict.restype = POINTER(PyDict_RS) 199 | 200 | 201 | def _load_rust_lib(recmpl=False): 202 | def load_compiled_lib(lib_path): 203 | global c_backend 204 | c_backend = ctypes.cdll.LoadLibrary(lib_path) 205 | config_ctypes() 206 | 207 | ext = {'darwin': '.dylib', 'win32': '.dll'}.get(sys.platform, '.so') 208 | pre = {'win32': ''}.get(sys.platform, 'lib') 209 | 210 | def get_from_site(): 211 | glob_pattern = "{}rustypy*{}".format(pre, ext) 212 | for p in sys.path: 213 | if p.endswith('site-packages'): 214 | lib_path = list(pathlib.Path(p).glob(glob_pattern)) 215 | if len(lib_path) > 0: 216 | return str(lib_path[0]) 217 | return None 218 | 219 | lib = get_from_site() 220 | if lib: 221 | load_compiled_lib(lib) 222 | return 223 | 224 | lib_file = "{}rustypy{}".format(pre, ext) 225 | root = pathlib.Path(str(importlib.import_module('librustypy').__file__)).parent 226 | lib = str(root.joinpath(lib_file)) 227 | if (not os.path.exists(lib)) or recmpl: 228 | import logging 229 | import subprocess 230 | import shutil 231 | logging.info(" library not found at: {}".format(lib)) 232 | logging.info(" compiling with Cargo") 233 | 234 | subprocess.run(['cargo', 'build', '--release'], cwd=str(root)) 235 | cp = os.path.join(str(root), 'target', 'release', lib_file) 236 | if os.path.exists(lib): 237 | os.remove(lib) 238 | shutil.copy(cp, lib) 239 | _load_rust_lib() 240 | else: 241 | from ..__init__ import __version__ as curr_ver 242 | # check that is the same version 243 | lib_ver = curr_ver 244 | # load the library 245 | if lib_ver != curr_ver: 246 | _load_rust_lib(recmpl=True) 247 | else: 248 | load_compiled_lib(lib) 249 | 250 | 251 | def get_rs_lib(): 252 | if not c_backend: 253 | _load_rust_lib() 254 | return c_backend 255 | -------------------------------------------------------------------------------- /src/rustypy/rswrapper/pytypes.py: -------------------------------------------------------------------------------- 1 | """PyTypes wrappers.""" 2 | 3 | import abc 4 | from collections import deque 5 | from enum import Enum, unique 6 | from collections import abc as abc_coll 7 | 8 | from rustypy.type_checkers import prev_to_37 9 | from .ffi_defs import * 10 | 11 | c_backend = get_rs_lib() 12 | 13 | 14 | class MissingTypeHint(TypeError): 15 | pass 16 | 17 | 18 | @unique 19 | class PyEquivType(Enum): 20 | String = 1 21 | Bool = 2 22 | Int = 3 23 | Double = 4 24 | Float = 5 25 | Tuple = 6 26 | List = 7 27 | Dict = 8 28 | 29 | 30 | def _dangling_pointer(*args, **kwargs): 31 | raise ReferenceError("rustypy: the underlying Rust type has been dropped") 32 | 33 | 34 | class PythonObjectMeta(type): 35 | 36 | @staticmethod 37 | def type_checking__python35_36(arg_t): 38 | kind = None 39 | if arg_t is str: 40 | kind = PyEquivType.String 41 | elif arg_t is bool: 42 | kind = PyEquivType.Bool 43 | elif arg_t is int: 44 | kind = PyEquivType.Int 45 | elif arg_t is Double or arg_t is float: 46 | kind = PyEquivType.Double 47 | elif arg_t is Float: 48 | kind = PyEquivType.Float 49 | elif issubclass(arg_t, Tuple): 50 | kind = PyEquivType.Tuple 51 | elif issubclass(arg_t, list): 52 | kind = PyEquivType.List 53 | elif issubclass(arg_t, dict): 54 | kind = PyEquivType.Dict 55 | return kind 56 | 57 | @staticmethod 58 | def type_checking__python37(arg_t): 59 | kind = None 60 | if arg_t is str: 61 | kind = PyEquivType.String 62 | elif arg_t is bool: 63 | kind = PyEquivType.Bool 64 | elif arg_t is int: 65 | kind = PyEquivType.Int 66 | elif arg_t is Double or arg_t is float: 67 | kind = PyEquivType.Double 68 | elif arg_t is Float: 69 | kind = PyEquivType.Float 70 | elif issubclass(arg_t, Tuple): 71 | kind = PyEquivType.Tuple 72 | elif hasattr(arg_t, "__origin__") and issubclass(arg_t.__origin__, (list, abc_coll.MutableSequence)): 73 | kind = PyEquivType.List 74 | elif hasattr(arg_t, "__origin__") and issubclass(arg_t.__origin__, (list, abc_coll.MutableMapping)): 75 | kind = PyEquivType.Dict 76 | return kind 77 | 78 | def __new__(mcs, cls_name, parents, attributes): 79 | new_class = super(PythonObjectMeta, mcs).__new__(mcs, cls_name, parents, attributes) 80 | if prev_to_37: 81 | setattr(new_class, "type_checking", mcs.type_checking__python35_36) 82 | else: 83 | setattr(new_class, "type_checking", mcs.type_checking__python37) 84 | return new_class 85 | 86 | 87 | class PythonObject(metaclass=PythonObjectMeta): 88 | 89 | def __init__(self, ptr): 90 | self._ptr = ptr 91 | 92 | def __del__(self): 93 | self.free() 94 | 95 | @abc.abstractmethod 96 | def free(self): 97 | pass 98 | 99 | def get_rs_obj(self): 100 | return self._ptr 101 | 102 | 103 | class PyString(PythonObject): 104 | 105 | def free(self): 106 | if hasattr(self, "_ptr"): 107 | c_backend.pystring_free(self._ptr) 108 | delattr(self, '_ptr') 109 | setattr(self, 'to_str', _dangling_pointer) 110 | 111 | def to_str(self): 112 | """Consumes the wrapper and returns a Python string. 113 | Afterwards is not necessary to destruct it as it has already 114 | been consumed.""" 115 | val = c_backend.pystring_get_str(self._ptr) 116 | delattr(self, '_ptr') 117 | setattr(self, 'to_str', _dangling_pointer) 118 | return val.decode("utf-8") 119 | 120 | @staticmethod 121 | def from_str(s: str): 122 | return c_backend.pystring_new(s.encode("utf-8")) 123 | 124 | 125 | class PyBool(PythonObject): 126 | 127 | def free(self): 128 | if hasattr(self, "_ptr"): 129 | c_backend.pybool_free(self._ptr) 130 | delattr(self, '_ptr') 131 | setattr(self, 'to_bool', _dangling_pointer) 132 | 133 | def to_bool(self): 134 | val = c_backend.pybool_get_val(self._ptr) 135 | if val == 0: 136 | val = False 137 | else: 138 | val = True 139 | self.free() 140 | return val 141 | 142 | @staticmethod 143 | def from_bool(val: bool): 144 | if val is True: 145 | return c_backend.pybool_new(1) 146 | else: 147 | return c_backend.pybool_new(0) 148 | 149 | 150 | def _to_pybool(arg): 151 | if arg: 152 | return c_backend.pyarg_from_bool(1) 153 | else: 154 | return c_backend.pyarg_from_bool(0) 155 | 156 | 157 | def _to_pystring(arg): 158 | return c_backend.pyarg_from_str(arg.encode("utf-8")) 159 | 160 | 161 | def _to_pytuple(signature): 162 | def dec(arg): 163 | return c_backend.pyarg_from_pytuple(PyTuple.from_tuple(arg, signature)) 164 | return dec 165 | 166 | 167 | def _to_pylist(signature): 168 | def dec(arg): 169 | return c_backend.pyarg_from_pylist(PyList.from_list(arg, signature)) 170 | return dec 171 | 172 | 173 | def _to_pydict(signature): 174 | def dec(arg): 175 | d = PyDict.from_dict(arg, signature) 176 | return c_backend.pyarg_from_pydict(d) 177 | return dec 178 | 179 | 180 | def _extract_value(pyarg, sig, depth=0): 181 | arg_t = PythonObject.type_checking(sig) 182 | if arg_t == PyEquivType.String: 183 | content = c_backend.pyarg_extract_owned_str(pyarg) 184 | pytype = c_backend.pystring_get_str(content).decode("utf-8") 185 | elif arg_t == PyEquivType.Bool: 186 | b = PyBool(c_backend.pyarg_extract_owned_bool(pyarg)) 187 | pytype = b.to_bool() 188 | elif arg_t == PyEquivType.Int: 189 | pytype = c_backend.pyarg_extract_owned_int(pyarg) 190 | elif arg_t is UnsignedLongLong: 191 | pytype = c_backend.pyarg_extract_owned_ulonglong(pyarg) 192 | elif arg_t == PyEquivType.Double: 193 | pytype = c_backend.pyarg_extract_owned_double(pyarg) 194 | elif arg_t == PyEquivType.Float: 195 | pytype = c_backend.pyarg_extract_owned_float(pyarg) 196 | elif arg_t == PyEquivType.Tuple: 197 | ptr = c_backend.pyarg_extract_owned_tuple(pyarg) 198 | t = PyTuple(ptr, sig) 199 | pytype = t.to_tuple(depth=depth + 1) 200 | elif arg_t == PyEquivType.List: 201 | ptr = c_backend.pyarg_extract_owned_list(pyarg) 202 | ls = PyList(ptr, sig) 203 | pytype = ls.to_list(depth=depth + 1) 204 | elif arg_t == PyEquivType.Dict: 205 | ptr = c_backend.pyarg_extract_owned_dict(pyarg) 206 | d = PyDict(ptr, sig) 207 | pytype = d.to_dict(depth=depth + 1) 208 | else: 209 | raise TypeError("rustypy: tried to extract invalid type: {}".format(arg_t)) 210 | return pytype 211 | 212 | 213 | class PyTuple(PythonObject): 214 | 215 | def __init__(self, ptr, signature, call_fn=None): 216 | super().__init__(ptr) 217 | self._ptr = ptr 218 | if not signature: 219 | raise MissingTypeHint( 220 | "rustypy: missing type hint for PyTuple unpacking in Python") 221 | if not issubclass(signature, Tuple): 222 | raise TypeError("rustypy: expecting rustypy Tuple definition, found `{}` instead" 223 | .format(signature)) 224 | self.signature = signature 225 | self.call_fn = call_fn 226 | 227 | def free(self): 228 | if hasattr(self, "_ptr"): 229 | c_backend.pytuple_free(self._ptr) 230 | delattr(self, '_ptr') 231 | setattr(self, 'to_tuple', _dangling_pointer) 232 | 233 | def to_tuple(self, depth=0): 234 | arity = c_backend.pytuple_len(self._ptr) 235 | if arity != len(self.signature) and self.call_fn: 236 | raise TypeError("rustypy: the type hint for returning tuple of fn `{}` " 237 | "and the return tuple value are not of " 238 | "the same length".format(self.call_fn._fn_name)) 239 | elif arity != len(self.signature): 240 | raise TypeError( 241 | "rustypy: type hint for PyTuple is of wrong length") 242 | tuple_elems = [] 243 | for pos, arg_t in enumerate(self.signature): 244 | pyarg = c_backend.pytuple_get_element(self._ptr, pos) 245 | pytype = _extract_value(pyarg, arg_t, depth=depth + 1) 246 | if pytype is None: 247 | raise TypeError("rustypy: subtype `{t}` of Tuple type is " 248 | "not supported".format(t=arg_t)) 249 | tuple_elems.append(pytype) 250 | self.free() 251 | return tuple(tuple_elems) 252 | 253 | @staticmethod 254 | def from_tuple(source: tuple, signature): 255 | try: 256 | if not issubclass(signature, Tuple): 257 | raise Exception 258 | except: 259 | raise TypeError("rustypy: type hint for PyTuple.from_tuple " 260 | "must be of rustypy.Tuple type") 261 | next_e = None 262 | cnt = len(source) - 1 263 | for i in range(0, len(source)): 264 | cnt = cnt - i 265 | sig = signature.element_type(cnt) 266 | arg_t = PythonObject.type_checking(sig) 267 | element = source[cnt] 268 | if arg_t == PyEquivType.String: 269 | pyarg = _to_pystring(element) 270 | elif arg_t == PyEquivType.Bool: 271 | pyarg = _to_pystring(element) 272 | elif arg_t == PyEquivType.Int: 273 | pyarg = c_backend.pyarg_from_int(element) 274 | elif arg_t == PyEquivType.Double: 275 | pyarg = c_backend.pyarg_from_double(element) 276 | elif arg_t == PyEquivType.Float: 277 | pyarg = c_backend.pyarg_from_float(element) 278 | elif arg_t == PyEquivType.Tuple: 279 | pyarg = _to_pytuple(sig)(element) 280 | elif arg_t == PyEquivType.List: 281 | pyarg = _to_pylist(sig)(element) 282 | elif arg_t == PyEquivType.Dict: 283 | pyarg = _to_pydict(sig)(element) 284 | else: 285 | raise TypeError("rustypy: subtype `{t}` of Tuple type is \ 286 | not supported".format(t=arg_t)) 287 | prev_e = c_backend.pytuple_new(cnt, pyarg) 288 | if next_e: 289 | c_backend.pytuple_push(next_e, prev_e) 290 | next_e = prev_e 291 | return prev_e 292 | 293 | 294 | class PyList(PythonObject): 295 | 296 | def __init__(self, ptr, signature, call_fn=None): 297 | super().__init__(ptr) 298 | self._ptr = ptr 299 | self._len = c_backend.pylist_len(self._ptr) 300 | if not signature: 301 | raise MissingTypeHint( 302 | "rustypy: missing type hint for PyList unpacking in Python") 303 | self.signature = signature 304 | self.call_fn = call_fn 305 | 306 | def free(self): 307 | if hasattr(self, "_ptr"): 308 | c_backend.pylist_free(self._ptr) 309 | delattr(self, '_ptr') 310 | setattr(self, 'to_list', _dangling_pointer) 311 | 312 | def to_list(self, depth=0): 313 | sig = self.signature.__args__[0] 314 | arg_t = PythonObject.type_checking(sig) 315 | pylist = deque() 316 | last = self._len - 1 317 | if arg_t == PyEquivType.String: 318 | for e in range(0, self._len): 319 | pyarg = c_backend.pylist_get_element(self._ptr, last) 320 | content = c_backend.pyarg_extract_owned_str(pyarg) 321 | pylist.appendleft( 322 | c_backend.pystring_get_str(content).decode("utf-8")) 323 | last -= 1 324 | elif arg_t == PyEquivType.Bool: 325 | for e in range(0, self._len): 326 | pyarg = c_backend.pylist_get_element(self._ptr, last) 327 | b = PyBool(c_backend.pyarg_extract_owned_bool(pyarg)) 328 | pylist.appendleft(b.to_bool()) 329 | last -= 1 330 | elif arg_t == PyEquivType.Int: 331 | for e in range(0, self._len): 332 | pyarg = c_backend.pylist_get_element(self._ptr, last) 333 | content = c_backend.pyarg_extract_owned_int(pyarg) 334 | pylist.appendleft(content) 335 | last -= 1 336 | elif arg_t == PyEquivType.Double: 337 | for e in range(0, self._len): 338 | pyarg = c_backend.pylist_get_element(self._ptr, last) 339 | content = c_backend.pyarg_extract_owned_double(pyarg) 340 | pylist.appendleft(content) 341 | last -= 1 342 | elif arg_t == PyEquivType.Float: 343 | for e in range(0, self._len): 344 | pyarg = c_backend.pylist_get_element(self._ptr, last) 345 | content = c_backend.pyarg_extract_owned_float(pyarg) 346 | pylist.appendleft(content) 347 | last -= 1 348 | elif arg_t == PyEquivType.Tuple: 349 | for e in range(0, self._len): 350 | pyarg = c_backend.pylist_get_element(self._ptr, last) 351 | ptr = c_backend.pyarg_extract_owned_tuple(pyarg) 352 | t = PyTuple(ptr, sig) 353 | pylist.appendleft(t.to_tuple(depth=depth + 1)) 354 | last -= 1 355 | elif arg_t == PyEquivType.List: 356 | for e in range(0, self._len): 357 | pyarg = c_backend.pylist_get_element(self._ptr, last) 358 | ptr = c_backend.pyarg_extract_owned_list(pyarg) 359 | l = PyList(ptr, sig) 360 | pylist.appendleft(l.to_list(depth=depth + 1)) 361 | last -= 1 362 | elif arg_t == PyEquivType.Dict: 363 | for e in range(0, self._len): 364 | pyarg = c_backend.pylist_get_element(self._ptr, last) 365 | ptr = c_backend.pyarg_extract_owned_dict(pyarg) 366 | d = PyDict(ptr, sig) 367 | pylist.appendleft(d.to_dict(depth=depth + 1)) 368 | last -= 1 369 | else: 370 | raise TypeError("rustypy: subtype `{t}` of List type is \ 371 | not supported".format(t=sig)) 372 | self.free() 373 | return list(pylist) 374 | 375 | @staticmethod 376 | def from_list(source: list, signature): 377 | sig = signature.__args__[0] 378 | arg_t = PythonObject.type_checking(sig) 379 | if arg_t == PyEquivType.String: 380 | fn = _to_pystring 381 | elif arg_t == PyEquivType.Bool: 382 | fn = _to_pybool 383 | elif arg_t == PyEquivType.Int: 384 | fn = c_backend.pyarg_from_int 385 | elif arg_t == PyEquivType.Double: 386 | fn = c_backend.pyarg_from_double 387 | elif arg_t == PyEquivType.Float: 388 | fn = c_backend.pyarg_from_float 389 | elif arg_t == PyEquivType.Tuple: 390 | fn = _to_pytuple(sig) 391 | elif arg_t == PyEquivType.List: 392 | fn = _to_pylist(sig) 393 | elif arg_t == PyEquivType.Dict: 394 | fn = _to_pydict(sig) 395 | else: 396 | raise TypeError("rustypy: subtype {t} of List type is \ 397 | not supported".format(t=sig)) 398 | pylist = c_backend.pylist_new(len(source)) 399 | for e in source: 400 | c_backend.pylist_push(pylist, fn(e)) 401 | return pylist 402 | 403 | 404 | class HashableTypeABC(abc.ABCMeta): 405 | __allowed = ['i64', 'i32', 'i16', 'i8', 406 | 'u64', 'u32', 'u16', 'u8', 407 | 'PyString', 'PyBool'] 408 | 409 | _doc = """Represents a hashable supported Rust type. 410 | Args: 411 | t (str): String representing the type, the following are supported: 412 | i64, i32, i16, i8, u32, u16, u8, PyString, PyBool 413 | """ 414 | 415 | def __call__(cls, t): 416 | if t in ['i64', 'i32', 'i16', 'i8', 'u64', 'u32', 'u16', 'u8']: 417 | pytype = int 418 | elif t == 'PyString': 419 | pytype = str 420 | elif t == 'PyBool': 421 | pytype = bool 422 | else: 423 | raise TypeError("rustypy: dictionary key must be one of the \ 424 | following types: {}".format("".join( 425 | [x + ', ' for x in cls.__allowed]))) 426 | new = type(t, (HashableTypeABC,), { 427 | '_type': t, '_pytype': pytype, '__doc__': cls._doc}) 428 | return new 429 | 430 | @classmethod 431 | def __subclasshook__(mcs, C): 432 | if mcs is HashableTypeABC: 433 | if hasattr(C, '_type') and C._type in mcs.__allowed: 434 | return True 435 | else: 436 | return False 437 | 438 | def is_hashable(self): 439 | if issubclass(self, str): 440 | return True 441 | elif issubclass(self, bool): 442 | return True 443 | elif issubclass(self, int): 444 | return True 445 | else: 446 | return False 447 | 448 | 449 | HashableType = HashableTypeABC( 450 | 'HashableType', (HashableTypeABC,), 451 | {"__doc__": HashableTypeABC._doc}) 452 | 453 | 454 | class PyDict(PythonObject): 455 | 456 | def __init__(self, ptr, signature, call_fn=None): 457 | super().__init__(ptr) 458 | self._ptr = ptr 459 | if not signature: 460 | raise MissingTypeHint( 461 | "rustypy: missing type hint for PyList unpacking in Python") 462 | self.signature = signature 463 | key_t = self.signature.__args__[0] 464 | if not issubclass(key_t, HashableTypeABC): 465 | TypeError("rustypy: the type corresponding to the key of a \ 466 | dictionary must be a subclass of rustypy.HashableType") 467 | self.call_fn = call_fn 468 | 469 | def free(self): 470 | if hasattr(self, "_ptr"): 471 | c_backend.pydict_free(self._ptr, self.key_rs_type) 472 | delattr(self, '_ptr') 473 | setattr(self, 'to_dict', _dangling_pointer) 474 | 475 | def to_dict(self, depth=0): 476 | key_t = self.signature.__args__[0]._type 477 | arg_t = self.signature.__args__[1] 478 | key_rs_t, _, fnk, key_py_t = PyDict.get_key_type_info(key_t) 479 | drain_iter = c_backend.pydict_get_drain(self._ptr, key_rs_t) 480 | 481 | # run the drain iterator while not a null pointer 482 | pydict, kv_tuple = [], True 483 | while kv_tuple: 484 | kv_tuple = c_backend.pydict_drain_element( 485 | drain_iter, key_rs_t) 486 | if not kv_tuple: 487 | break 488 | key = c_backend.pydict_get_kv(0, kv_tuple) 489 | val = c_backend.pydict_get_kv(1, kv_tuple) 490 | t = (fnk(key), _extract_value(val, arg_t)) 491 | c_backend.pydict_free_kv(kv_tuple) 492 | pydict.append(t) 493 | self.free() 494 | return dict(pydict) 495 | 496 | @staticmethod 497 | def from_dict(source: dict, signature): 498 | key_t = signature.__args__[0] 499 | sig = signature.__args__[1] 500 | arg_t = PythonObject.type_checking(sig) 501 | if not issubclass(key_t, HashableTypeABC): 502 | TypeError("rustypy: the type corresponding to the key of a \ 503 | dictionary must be a subclass of rustypy.HashableType") 504 | key_rs_t, fnk, _, _ = PyDict.get_key_type_info(key_t._type) 505 | if arg_t == PyEquivType.String: 506 | fnv = _to_pystring 507 | elif arg_t == PyEquivType.Bool: 508 | fnv = _to_pybool 509 | elif arg_t == PyEquivType.Int: 510 | fnv = c_backend.pyarg_from_int 511 | elif arg_t == PyEquivType.Double: 512 | fnv = c_backend.pyarg_from_double 513 | elif arg_t == PyEquivType.Float: 514 | fnv = c_backend.pyarg_from_float 515 | elif arg_t == PyEquivType.Tuple: 516 | fnv = _to_pytuple(sig) 517 | elif arg_t == PyEquivType.List: 518 | fnv = _to_pylist(sig) 519 | elif arg_t == PyEquivType.Dict: 520 | fnv = _to_pydict(sig) 521 | else: 522 | raise TypeError("rustypy: subtype {t} of List type is \ 523 | not supported".format(t=arg_t)) 524 | pydict = c_backend.pydict_new(key_rs_t) 525 | for k, v in source.items(): 526 | c_backend.pydict_insert(pydict, key_rs_t, fnk(k), fnv(v)) 527 | return pydict 528 | 529 | @staticmethod 530 | def get_key_type_info(key_t): 531 | if key_t == 'i8': 532 | key_rs_t = c_backend.pydict_get_key_type(1) 533 | fnk = c_backend.pyarg_from_int 534 | fne = c_backend.pyarg_extract_owned_int 535 | key_py_t = int 536 | elif key_t == 'u8': 537 | key_rs_t = c_backend.pydict_get_key_type(2) 538 | fnk = c_backend.pyarg_from_int 539 | fne = c_backend.pyarg_extract_owned_int 540 | key_py_t = int 541 | elif key_t == 'i16': 542 | key_rs_t = c_backend.pydict_get_key_type(3) 543 | fnk = c_backend.pyarg_from_int 544 | fne = c_backend.pyarg_extract_owned_int 545 | key_py_t = int 546 | elif key_t == 'u16': 547 | key_rs_t = c_backend.pydict_get_key_type(4) 548 | fnk = c_backend.pyarg_from_int 549 | fne = c_backend.pyarg_extract_owned_int 550 | key_py_t = int 551 | elif key_t == 'i32': 552 | key_rs_t = c_backend.pydict_get_key_type(5) 553 | fnk = c_backend.pyarg_from_int 554 | fne = c_backend.pyarg_extract_owned_int 555 | key_py_t = int 556 | elif key_t == 'u32': 557 | key_rs_t = c_backend.pydict_get_key_type(6) 558 | fnk = c_backend.pyarg_from_int 559 | fne = c_backend.pyarg_extract_owned_int 560 | key_py_t = int 561 | elif key_t == 'i64': 562 | key_rs_t = c_backend.pydict_get_key_type(7) 563 | fnk = c_backend.pyarg_from_int 564 | fne = c_backend.pyarg_extract_owned_int 565 | key_py_t = int 566 | elif key_t == 'u64': 567 | key_rs_t = c_backend.pydict_get_key_type(8) 568 | fnk = c_backend.pyarg_from_ulonglong 569 | fne = c_backend.pyarg_extract_owned_ulonglong 570 | key_py_t = UnsignedLongLong 571 | elif key_t == 'PyBool': 572 | key_rs_t = c_backend.pydict_get_key_type(11) 573 | fnk = _to_pybool 574 | fne = c_backend.pyarg_extract_owned_bool 575 | key_py_t = bool 576 | elif key_t == 'PyString': 577 | key_rs_t = c_backend.pydict_get_key_type(12) 578 | fnk = _to_pystring 579 | fne = c_backend.pyarg_extract_owned_string 580 | key_py_t = str 581 | else: 582 | raise TypeError("rustypy: the type corresponding to the key of a \ 583 | dictionary must be a subclass of rustypy.HashableType") 584 | return key_rs_t, fnk, fne, key_py_t 585 | 586 | @property 587 | def key_rs_type(self): 588 | if not hasattr(self, '_key_rs_type'): 589 | rst, _, _, pyt = PyDict.get_key_type_info( 590 | self.signature.__args__[0]._type) 591 | self._key_rs_type = rst 592 | self._key_py_type = pyt 593 | return self._key_rs_type 594 | 595 | @property 596 | def key_py_type(self): 597 | if not hasattr(self, '_key_py_type'): 598 | rst, _, _, pyt = PyDict.get_key_type_info( 599 | self.signature.__args__[0]._type) 600 | self._key_rs_type = rst 601 | self._key_py_type = pyt 602 | return self._key_py_type 603 | 604 | 605 | from .rswrapper import Float, Double, UnsignedLongLong, Tuple 606 | -------------------------------------------------------------------------------- /src/rustypy/rswrapper/rswrapper.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Generates code for calling Rust from Python.""" 3 | 4 | import os.path 5 | import re 6 | import typing 7 | from collections import namedtuple 8 | 9 | from .ffi_defs import * 10 | from .ffi_defs import get_rs_lib 11 | from .pytypes import MissingTypeHint, PyBool, PyDict, PyList, PyString, PyTuple 12 | from ..type_checkers import type_checkers, is_map_like, is_seq_like 13 | 14 | c_backend = get_rs_lib() 15 | 16 | # ==================== # 17 | # Conversion Funcs # 18 | # ==================== # 19 | 20 | FIND_TYPE = re.compile("type\((.*)\)") 21 | 22 | RustType = namedtuple('RustType', ['equiv', 'ref', 'mutref', 'raw']) 23 | 24 | Float = type('Float', (float,), {'_definition': ctypes.c_float}) 25 | Double = type('Double', (float,), {'_definition': ctypes.c_double}) 26 | UnsignedLongLong = type('UnsignedLongLong', (int,), { 27 | '_definition': ctypes.c_ulonglong}) 28 | 29 | 30 | class TupleMeta(type): 31 | 32 | def __new__(mcs, name, bases, namespace, parameters=None): 33 | tuple_cls = super().__new__(mcs, name, bases, namespace) 34 | tuple_cls.__iter_cnt = 0 35 | if not parameters: 36 | tuple_cls.__params = None 37 | return tuple_cls 38 | tuple_cls.__params = [] 39 | 40 | @type_checkers 41 | def check_type(arg_t, **checkers): 42 | is_map_like = checkers["map_like"] 43 | is_seq_like = checkers["seq_like"] 44 | is_generic = checkers["generic"] 45 | 46 | type_annotation = None 47 | if arg_t is str: 48 | type_annotation = str 49 | elif arg_t is bool: 50 | type_annotation = bool 51 | elif arg_t is int: 52 | type_annotation = int 53 | elif arg_t is UnsignedLongLong: 54 | type_annotation = UnsignedLongLong 55 | elif arg_t is Double or arg_t is float: 56 | type_annotation = Double 57 | elif arg_t is Float: 58 | type_annotation = Float 59 | elif is_generic(arg_t): 60 | type_annotation = arg_t 61 | elif issubclass(arg_t, Tuple): 62 | type_annotation = arg_t 63 | elif is_seq_like(arg_t): 64 | type_annotation = arg_t 65 | elif is_map_like(arg_t): 66 | type_annotation = arg_t 67 | else: 68 | raise TypeError("rustypy: subtype `{t}` of Tuple type is \ 69 | not supported".format(t=arg_t)) 70 | return type_annotation 71 | 72 | for arg_t in parameters: 73 | param = check_type(arg_t) 74 | tuple_cls.__params.append(param) 75 | 76 | return tuple_cls 77 | 78 | def __init__(cls, *args, **kwds): 79 | pass 80 | 81 | def __len__(self): 82 | return len(self.__params) 83 | 84 | def __getitem__(self, parameters): 85 | if self.__params is not None: 86 | raise TypeError("Cannot re-parameterize %r" % (self,)) 87 | if not isinstance(parameters, tuple): 88 | parameters = (parameters,) 89 | return self.__class__(self.__name__, self.__bases__, dict(self.__dict__), parameters) 90 | 91 | def __repr__(self): 92 | if self.__params is None: 93 | return "rutypy.Tuple" 94 | inner = "".join(["%r, " % e if i + 1 < len(self.__params) else repr(e) 95 | for i, e in enumerate(self.__params)]) 96 | return "rustypy.Tuple[{}]".format(inner) 97 | 98 | def element_type(self, pos): 99 | return self.__params[pos] 100 | 101 | def __subclasscheck__(self, cls): 102 | if cls is typing.Any: 103 | return True 104 | if isinstance(cls, tuple): 105 | return True 106 | if not isinstance(cls, TupleMeta): 107 | return False 108 | else: 109 | return True 110 | 111 | def __iter__(self): 112 | return self 113 | 114 | def __next__(self): 115 | if not self.__params: 116 | return StopIteration() 117 | if self.__iter_cnt < len(self.__params): 118 | e = self.__params[self.__iter_cnt] 119 | self.__iter_cnt += 1 120 | return e 121 | else: 122 | self.__iter_cnt = 0 123 | raise StopIteration() 124 | 125 | 126 | class Tuple(metaclass=TupleMeta): 127 | __slots__ = () 128 | 129 | def __new__(cls, *args, **kwds): 130 | raise TypeError("Cannot subclass %r" % (cls,)) 131 | 132 | 133 | class OpaquePtr(object): 134 | pass 135 | 136 | 137 | def _get_signature_types(params): 138 | def inner_types(t): 139 | t = t.strip() 140 | mutref, ref, raw = False, False, False 141 | if "&mut" in t: 142 | type_ = t.replace("&mut", '').strip() 143 | mutref = True 144 | elif "*mut" in t: 145 | type_ = t.replace("*mut", '').strip() 146 | mutref, raw = True, True 147 | elif "&" in t: 148 | type_ = t.replace('&', '').strip() 149 | ref = True 150 | elif "*const" in t: 151 | type_ = t.replace("*const", '').strip() 152 | ref, raw = True, True 153 | else: 154 | type_ = t 155 | try: 156 | equiv = RS_TYPE_CONVERSION[type_] 157 | except: 158 | raise TypeError("rustypy: type not supported: {}".format(type_)) 159 | else: 160 | if equiv == 'int': 161 | return RustType(equiv=int, ref=ref, mutref=mutref, raw=raw) 162 | elif equiv == 'float': 163 | return RustType(equiv=Float, ref=ref, mutref=mutref, raw=raw) 164 | elif equiv == 'double': 165 | return RustType(equiv=Double, ref=ref, mutref=mutref, raw=raw) 166 | elif equiv == 'str': 167 | return RustType(equiv=str, ref=ref, mutref=mutref, raw=raw) 168 | elif equiv == 'bool': 169 | return RustType(equiv=bool, ref=ref, mutref=mutref, raw=raw) 170 | elif equiv == 'tuple': 171 | return RustType(equiv=tuple, ref=ref, mutref=mutref, raw=raw) 172 | elif equiv == 'list': 173 | return RustType(equiv=list, ref=ref, mutref=mutref, raw=raw) 174 | elif equiv == 'OpaquePtr': 175 | return RustType(equiv=OpaquePtr, ref=ref, mutref=mutref, raw=raw) 176 | elif equiv == 'None': 177 | return RustType(equiv=None, ref=False, mutref=False, raw=False) 178 | 179 | def non_empty(param): 180 | return param != "()" 181 | 182 | params = [x for x in params.split(';') if x != ''] 183 | param_types = [] 184 | for p in filter(non_empty, params): 185 | param_types.append(re.search(FIND_TYPE, p).group(1)) 186 | param_types[-1] = inner_types(param_types[-1]) 187 | return param_types 188 | 189 | 190 | def _get_ptr_to_C_obj(obj, sig=None): 191 | if isinstance(obj, bool): 192 | return PyBool.from_bool(obj) 193 | elif isinstance(obj, int): 194 | return ctypes.c_longlong(obj) 195 | elif isinstance(obj, Float): 196 | return ctypes.c_float(obj) 197 | elif isinstance(obj, Double) or isinstance(obj, float): 198 | return ctypes.c_double(obj) 199 | elif isinstance(obj, str): 200 | return PyString.from_str(obj) 201 | elif isinstance(obj, tuple): 202 | if not sig: 203 | raise MissingTypeHint( 204 | "rustypy: tuple type arguments require a type hint") 205 | return PyTuple.from_tuple(obj, sig) 206 | elif isinstance(obj, list): 207 | if not sig: 208 | raise MissingTypeHint( 209 | "rustypy: list type arguments require a type hint") 210 | return PyList.from_list(obj, sig) 211 | elif isinstance(obj, dict): 212 | if not sig: 213 | raise MissingTypeHint( 214 | "rustypy: dict type arguments require a type hint") 215 | if not is_map_like(sig): 216 | raise TypeError( 217 | "rustypy: the type hint must be of typing.Dict type") 218 | return PyDict.from_dict(obj, sig) 219 | elif isinstance(obj, OpaquePtr): 220 | if not sig: 221 | raise MissingTypeHint( 222 | "rustypy: raw pointer type arguments require type information \ 223 | for proper type coercion") 224 | raise NotImplementedError 225 | 226 | 227 | def _extract_pytypes(ref, sig=False, call_fn=None, depth=0): 228 | if isinstance(ref, int): 229 | return ref 230 | elif isinstance(ref, float): 231 | return ref 232 | elif isinstance(ref, POINTER(ctypes.c_longlong)): 233 | return ref.contents 234 | elif isinstance(ref, POINTER(ctypes.c_float)): 235 | return ref.contents 236 | elif isinstance(ref, POINTER(ctypes.c_double)): 237 | return ref.contents 238 | elif isinstance(ref, POINTER(PyTuple_RS)): 239 | pyobj = PyTuple(ref, sig, call_fn=call_fn) 240 | val = pyobj.to_tuple(depth) 241 | return val 242 | elif isinstance(ref, POINTER(PyString_RS)): 243 | pyobj = PyString(ref) 244 | val = pyobj.to_str() 245 | return val 246 | elif isinstance(ref, POINTER(PyBool_RS)): 247 | pyobj = PyBool(ref) 248 | val = pyobj.to_bool() 249 | return val 250 | elif isinstance(ref, POINTER(PyList_RS)): 251 | pyobj = PyList(ref, sig, call_fn=call_fn) 252 | val = pyobj.to_list(depth) 253 | return val 254 | elif isinstance(ref, POINTER(PyDict_RS)): 255 | pyobj = PyDict(ref, sig, call_fn=call_fn) 256 | val = pyobj.to_dict(depth) 257 | return val 258 | elif isinstance(ref, POINTER(Raw_RS)): 259 | raise NotImplementedError 260 | else: 261 | raise TypeError("rustypy: return type not supported") 262 | 263 | 264 | # ============================= # 265 | # Helper classes and funcs # 266 | # ============================= # 267 | 268 | 269 | def get_crate_entry(mod): 270 | manifest = os.path.join(mod, 'Cargo.toml') 271 | if os.path.exists(manifest): 272 | rgx_lib = re.compile(r'\[lib\]') 273 | rgx_path = re.compile(r'path(\W+|)=(\W+|)[\'\"](?P.*)[\'\"]') 274 | inlibsection, entry = False, None 275 | with open(manifest, 'r') as f: 276 | for l in f: 277 | if inlibsection: 278 | entry = re.match(rgx_path, l) 279 | if entry: 280 | entry = entry.group('entry') 281 | entry = os.path.join(*entry.split('/')) 282 | break 283 | elif not inlibsection and re.search(rgx_lib, l): 284 | inlibsection = True 285 | if not entry: 286 | entry = os.path.join('src', 'lib.rs') 287 | return os.path.join(mod, entry) 288 | else: 289 | if os.path.isfile(mod) and os.path.basename(mod).endswith('.rs'): 290 | return mod 291 | else: 292 | default = os.path.join(mod, 'lib.rs') 293 | if os.path.exists(default): 294 | return default 295 | else: 296 | raise OSError( 297 | "rustypy: couldn't find lib.rs in the specified directory") 298 | 299 | 300 | def bind_rs_crate_funcs(mod, lib, prefixes=None): 301 | if not c_backend: 302 | get_rs_lib() 303 | if not os.path.exists(mod): 304 | raise OSError('rustypy: `{}` path does not exist'.format(mod)) 305 | elif not os.path.exists(lib): 306 | raise OSError( 307 | 'rustypy: `{}` compiled library file does not exist'.format(lib)) 308 | entry_point = get_crate_entry(mod) 309 | return RustBinds(entry_point, lib, prefixes=prefixes) 310 | 311 | 312 | class KrateData(object): 313 | 314 | def __init__(self, prefixes): 315 | self.obj = c_backend.krate_data_new(prefixes) 316 | 317 | def __enter__(self): 318 | return self 319 | 320 | def __exit__(self, exc_type, exc_value, traceback): 321 | c_backend.krate_data_free(self.obj) 322 | 323 | def __iter__(self): 324 | self._idx = 0 325 | self._len = c_backend.krate_data_len(self.obj) 326 | return self 327 | 328 | def __next__(self): 329 | if (self._len - 1) == -1 or self._idx > (self._len - 1): 330 | self._idx = 0 331 | raise StopIteration 332 | val = c_backend.krate_data_iter(self.obj, self._idx) 333 | self._idx += 1 334 | return PyString(val) 335 | 336 | 337 | class RustBinds(object): 338 | """Main binding generator class.""" 339 | 340 | def __init__(self, entry_point, compiled_lib, prefixes=None): 341 | if prefixes is None: 342 | prefixes = ["python_bind_"] 343 | self._FFI = ctypes.cdll.LoadLibrary(compiled_lib) 344 | if isinstance(prefixes, str): 345 | p = [prefixes] 346 | elif isinstance(prefixes, list): 347 | p = prefixes 348 | if len(p) == 0: 349 | raise ValueError("rustypy: optional prefixes list cannot be empty") 350 | else: 351 | p = ["python_bind_"] 352 | p = PyList.from_list(p, typing.List[str]) 353 | self._krate_data = KrateData(p) 354 | entry = PyString.from_str(entry_point) 355 | ret_msg = c_backend.parse_src(entry, self._krate_data.obj) 356 | if ret_msg: 357 | raise Exception( 358 | "rustypy: failed to generate Rust bindings, failed with error:\n" 359 | "{}".format(PyString(ret_msg).to_str())) 360 | prepared_funcs = {} 361 | with self._krate_data as krate: 362 | for e in krate: 363 | decl = e.to_str() 364 | if decl == "NO_IDX_ERROR": 365 | break 366 | for prefix in prefixes: 367 | s = decl.split(prefix) 368 | if len(s) == 2: 369 | name, decl = s[0], s[1] 370 | break 371 | name, params = decl.split('::', maxsplit=1) 372 | name = prefix + name 373 | params = _get_signature_types(params) 374 | fn = getattr(self._FFI, "{}".format(name)) 375 | RustBinds.decl_C_args(fn, params) 376 | prepared_funcs[name] = self.FnCall(name, params, self._FFI) 377 | for name, fn in prepared_funcs.items(): 378 | setattr(self, name, fn) 379 | 380 | class FnCall(object): 381 | 382 | def __init__(self, name, argtypes, lib): 383 | self._rs_fn = getattr(lib, name) 384 | self._fn_name = name 385 | self.__type_hints = {'real_return': argtypes.pop(), 'real_argtypes': argtypes} 386 | 387 | def __call__(self, *args, **kwargs): 388 | if kwargs: 389 | return_ref = kwargs.get('return_ref') 390 | get_contents = kwargs.get('get_contents') 391 | else: 392 | return_ref = False 393 | get_contents = False 394 | num_args = len(self.argtypes) 395 | given_args = len(args) 396 | if given_args != num_args: 397 | raise TypeError("rustypy: {}() takes exactly {} " 398 | "arguments ({} given)".format( 399 | self._fn_name, num_args, given_args)) 400 | prep_args = [] 401 | for x, a in enumerate(args): 402 | p = self.argtypes[x] 403 | if p.ref or p.mutref: 404 | sig = self.get_argtype(x) 405 | ref = _get_ptr_to_C_obj(a, sig=sig) 406 | prep_args.append(ref) 407 | elif isinstance(a, bool): 408 | ref = _get_ptr_to_C_obj(a) 409 | prep_args.append(ref) 410 | elif isinstance(a, str): 411 | ref = _get_ptr_to_C_obj(a) 412 | prep_args.append(ref) 413 | elif isinstance(a, int) or isinstance(a, float): 414 | prep_args.append(a) 415 | else: 416 | raise TypeError("rustypy: argument #{} type of `{}` passed to " 417 | "function `{}` not supported".format( 418 | x, a, self._fn_name)) 419 | result = self._rs_fn(*prep_args) 420 | if not return_ref: 421 | try: 422 | python_result = _extract_pytypes( 423 | result, call_fn=self, sig=self.restype) 424 | except MissingTypeHint: 425 | raise TypeError("rustypy: must add return type of " 426 | "function `{}`".format(self._fn_name)) 427 | return python_result 428 | elif get_contents: 429 | arg_refs = [] 430 | for x, r in enumerate(prep_args): 431 | if isinstance(r, POINTER(PyString_RS)): 432 | arg_refs.append(_extract_pytypes(r, call_fn=self)) 433 | elif isinstance(r, POINTER(PyBool_RS)): 434 | arg_refs.append(_extract_pytypes(r, call_fn=self)) 435 | elif isinstance(r, POINTER(ctypes.c_longlong)): 436 | arg_refs.append(_extract_pytypes(r, call_fn=self)) 437 | elif isinstance(r, POINTER(ctypes.c_float)): 438 | arg_refs.append(_extract_pytypes(r, call_fn=self)) 439 | elif isinstance(r, POINTER(ctypes.c_double)): 440 | arg_refs.append(_extract_pytypes(r, call_fn=self)) 441 | elif isinstance(r, POINTER(PyTuple_RS)): 442 | arg_refs.append(_extract_pytypes( 443 | r, call_fn=self, sig=self.get_argtype(x))) 444 | elif isinstance(r, POINTER(PyList_RS)): 445 | arg_refs.append(_extract_pytypes( 446 | r, call_fn=self, sig=self.get_argtype(x))) 447 | elif isinstance(r, POINTER(PyDict_RS)): 448 | arg_refs.append(_extract_pytypes( 449 | r, call_fn=self, sig=self.get_argtype(x))) 450 | else: 451 | arg_refs.append(r.value) 452 | return result, arg_refs 453 | else: 454 | arg_refs = [] 455 | for x, r in enumerate(prep_args): 456 | arg_refs.append(r) 457 | return result, arg_refs 458 | 459 | @property 460 | def real_restype(self): 461 | return self.__type_hints['real_return'] 462 | 463 | @property 464 | def restype(self): 465 | try: 466 | return self.__type_hints['return'] 467 | except KeyError: 468 | return 469 | 470 | @restype.setter 471 | def restype(self, annotation): 472 | self.__type_hints['return'] = annotation 473 | if is_map_like(annotation): 474 | real_t = self.__type_hints['real_return'] 475 | self.__type_hints['real_return'] = RustType( 476 | equiv=dict, ref=real_t.ref, mutref=real_t.mutref, raw=real_t.raw) 477 | r_args = [x for x in self.__type_hints['real_argtypes']] 478 | r_args.append(self.real_restype) 479 | RustBinds.decl_C_args(self._rs_fn, r_args) 480 | 481 | @property 482 | def argtypes(self): 483 | try: 484 | return self.__type_hints['real_argtypes'] 485 | except KeyError: 486 | return 487 | 488 | @argtypes.setter 489 | def argtypes(self): 490 | raise AttributeError( 491 | "rustypy: private attribute, cannot be set directly") 492 | 493 | def add_argtype(self, position, hint): 494 | types = self.__type_hints.setdefault( 495 | 'argtypes', [None] * len(self.argtypes)) 496 | real_t = self.argtypes[position] 497 | if real_t.equiv is list and not is_seq_like(hint): 498 | raise TypeError("rustypy: type hint for argument {n} of function {fn} \ 499 | must be of typing.List type") 500 | elif real_t.equiv is OpaquePtr and is_map_like(hint): 501 | self.__type_hints['real_argtypes'][position] = RustType( 502 | equiv=dict, ref=real_t.ref, mutref=real_t.mutref, raw=real_t.raw) 503 | r_args = [x for x in self.__type_hints['real_argtypes']] 504 | r_args.append(self.real_restype) 505 | RustBinds.decl_C_args(self._rs_fn, r_args) 506 | types[position] = hint 507 | 508 | def get_argtype(self, position): 509 | hints = self.__type_hints.get('argtypes') 510 | if hints: 511 | return hints[position] 512 | 513 | @staticmethod 514 | def decl_C_args(FFI, params): 515 | restype = None 516 | argtypes = [] 517 | for x, p in enumerate(params, 1): 518 | if p.equiv is None: 519 | add_p = c_void_p 520 | elif issubclass(p.equiv, bool): 521 | add_p = PyBool_RS 522 | elif issubclass(p.equiv, int): 523 | add_p = ctypes.c_longlong 524 | elif issubclass(p.equiv, float): 525 | add_p = p.equiv._definition 526 | elif issubclass(p.equiv, str): 527 | add_p = PyString_RS 528 | elif issubclass(p.equiv, tuple): 529 | add_p = PyTuple_RS 530 | elif issubclass(p.equiv, list): 531 | add_p = PyList_RS 532 | elif issubclass(p.equiv, dict): 533 | add_p = PyDict_RS 534 | elif issubclass(p.equiv, OpaquePtr): 535 | add_p = Raw_RS 536 | if p.mutref or p.ref: 537 | add_p = POINTER(add_p) 538 | if x <= (len(params) - 1): 539 | argtypes.append(add_p) 540 | else: 541 | restype = add_p 542 | setattr(FFI, "restype", restype) 543 | if len(argtypes) > 0: 544 | setattr(FFI, "argtypes", tuple(argtypes)) 545 | -------------------------------------------------------------------------------- /src/rustypy/scripts.py: -------------------------------------------------------------------------------- 1 | def cli(): 2 | import argparse 3 | import os 4 | import pip 5 | from importlib import import_module 6 | from rustypy.pywrapper import RustFuncGen 7 | # error messages 8 | _ext_err = "rustypy: error: target language and file extension " + \ 9 | "are not coherent" 10 | _pckg_err = "rustypy: error: provide the name of the package " + \ 11 | "or the path to the package" 12 | _not_found_err = "rustypy: error: package is not installed, you can " + \ 13 | "install packages in development mode using `pip install -e `" 14 | # CLI 15 | parser = argparse.ArgumentParser( 16 | prog="rustypy", 17 | description="Generates bindings from/to Rust/Python for the specified " 18 | + "package or module.") 19 | parser.add_argument( 20 | "lang", 21 | help="target language of the bindings generated, \ 22 | ie. `python` to generate binds to Python functions from Rust", 23 | choices=['rust', 'python']) 24 | parser.add_argument( 25 | "path", 26 | help="absolute path or name of the package/module (must be available \ 27 | from the Python or Cargo, in case of Rust, path") 28 | parser.add_argument( 29 | "--prefixes", 30 | help="optional function prefixes argument (only the functions with \ 31 | those prefixes) will be parsed (check the documentation for more \ 32 | information about default behaviour)", 33 | nargs="*") 34 | group1 = parser.add_mutually_exclusive_group(required=True) 35 | group1.add_argument("-m", "--module", action="store_true", 36 | help="it's a single module, default is a package") 37 | group1.add_argument("-p", "--package", action="store_true", 38 | help="it's a a package (default option)") 39 | args = parser.parse_args() 40 | if args.module: 41 | ismodule = True 42 | else: 43 | ismodule = False 44 | lang = args.lang 45 | path = args.path 46 | prefixes = args.prefixes 47 | # run script 48 | if os.sep in path and not os.path.exists(path): 49 | SystemExit("rustypy: error: the path does not exist") 50 | if lang == 'rust': 51 | raise NotImplementedError( 52 | """\ 53 | rustypy: rust bind generator from the command line not implemented, use 54 | the dynamic generator instead (check the library documentation for more 55 | information).""") 56 | else: 57 | ext = ".py" 58 | pckg, module = False, False 59 | if ismodule: 60 | if "." not in path: 61 | module, is_path = path, False 62 | if path[-3:] != ext: 63 | raise SystemExit(_ext_err) 64 | else: 65 | module, is_path = True, True 66 | else: 67 | if "." in path: 68 | raise SystemExit(_pckg_err) 69 | elif os.sep in path: 70 | pckg, is_path = True, True 71 | else: 72 | pckg, is_path = path, False 73 | if lang == 'python': 74 | if is_path and pckg: 75 | RustFuncGen(with_path=path, prefixes=prefixes) 76 | elif is_path and module: 77 | RustFuncGen(with_path=path, module=True, prefixes=prefixes) 78 | elif pckg: 79 | location = None 80 | for x in pip.get_installed_distributions(local_only=True): 81 | if x._key == pckg: 82 | location = x.location 83 | break 84 | if not location: 85 | raise SystemExit(_not_found_err) 86 | RustFuncGen(with_path=location, prefixes=prefixes) 87 | elif module: 88 | mod = import_module(module) 89 | RustFuncGen(module=mod, prefixes=prefixes) 90 | if ismodule: 91 | print("rustypy: binds for module `{}` generated".format(path)) 92 | else: 93 | print("rustypy: binds for package `{}` generated".format(path)) 94 | 95 | 96 | def get_version(): 97 | import pkg_resources 98 | try: 99 | rustypy_ver = pkg_resources.require("rustypy")[0].version 100 | except: 101 | import os 102 | import re 103 | p = os.path.join(os.path.dirname(__file__), '__init__.py') 104 | rustypy_ver = re.compile(r"^__version__ = '(.*)'") 105 | with open(p) as f: 106 | for l in f: 107 | ver = re.match(rustypy_ver, l) 108 | if ver: 109 | rustypy_ver = ver.group(1) 110 | break 111 | return rustypy_ver 112 | -------------------------------------------------------------------------------- /src/rustypy/type_checkers.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import sys 3 | import typing 4 | from collections import abc 5 | 6 | prev_to_37 = sys.version_info[0:2] <= (3, 6) 7 | if prev_to_37: 8 | def is_map_like(arg_t): 9 | if hasattr(arg_t, "__origin__"): 10 | return issubclass(arg_t.__origin__, (dict, abc.MutableMapping)) 11 | return issubclass(arg_t.__class__, (dict, abc.MutableMapping)) 12 | 13 | 14 | def is_seq_like(arg_t): 15 | if hasattr(arg_t, "__origin__"): 16 | return issubclass(arg_t.__origin__, (list, abc.MutableSequence)) 17 | return issubclass(arg_t.__class__, (list, abc.MutableSequence)) 18 | 19 | 20 | def is_generic(arg_t): 21 | return arg_t.__class__ is typing.GenericMeta 22 | else: 23 | def is_map_like(arg_t): 24 | if hasattr(arg_t, "__origin__"): 25 | return issubclass(arg_t.__origin__, (dict, abc.MutableMapping)) 26 | return False 27 | 28 | 29 | def is_seq_like(arg_t): 30 | if hasattr(arg_t, "__origin__"): 31 | return issubclass(arg_t.__origin__, (list, abc.MutableSequence)) 32 | return False 33 | 34 | 35 | def is_generic(arg_t): 36 | if hasattr(arg_t, "__origin__"): 37 | return arg_t.__origin__ is typing.Generic 38 | return False 39 | 40 | 41 | def type_checkers(func): 42 | @functools.wraps(func) 43 | def checker(*args, **kwargs): 44 | 45 | checkers = { 46 | "map_like": is_map_like, 47 | "seq_like": is_seq_like, 48 | "generic": is_generic, 49 | } 50 | 51 | kwargs.update(checkers) 52 | return func(*args, **kwargs) 53 | return checker 54 | -------------------------------------------------------------------------------- /tests/py_test_lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test_lib_py" 3 | version = "0.0.1" 4 | edition = "2015" 5 | 6 | [dependencies] 7 | cpython = { version="~0.3.0", features=["python3-sys"] } 8 | libc = "~0.2.62" 9 | rustypy = { path = "../../src/librustypy" } 10 | 11 | [lib] 12 | name = "test_lib_py" 13 | path = "./lib.rs" 14 | -------------------------------------------------------------------------------- /tests/py_test_lib/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate rustypy; 2 | extern crate cpython; 3 | extern crate libc; 4 | 5 | mod nested_types; 6 | mod primitives; 7 | mod submodules; 8 | mod test_package; 9 | -------------------------------------------------------------------------------- /tests/py_test_lib/nested_types.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | extern crate cpython; 3 | extern crate rustypy; 4 | 5 | use std::collections::HashMap; 6 | 7 | use libc::c_long; 8 | use cpython::{Python, ToPyObject, PythonObject, PyObject, PyLong, PyString}; 9 | 10 | #[test] 11 | fn nested_types() { 12 | use test_package::rustypy_pybind::PyModules; 13 | 14 | let gil = Python::acquire_gil(); 15 | let py = gil.python(); 16 | let module: PyModules = PyModules::new(&py); 17 | let basics = module.basics.nested_types; 18 | 19 | // Vec<(c_double, bool)> -> Vec 20 | let arg = vec![(0.2, true), (0.3, true), (0.5, true)]; 21 | let answ = basics.list1(arg); 22 | assert_eq!(answ, vec![String::from("passed")]); 23 | 24 | // HashMap -> HashMap 25 | let mut dict = HashMap::new(); 26 | dict.insert(String::from("first_key"), 1); 27 | let answ = basics.dict1(dict); 28 | let mut cmp = HashMap::new(); 29 | cmp.insert(String::from("first_key"), 2); 30 | assert_eq!(answ, cmp); 31 | 32 | // HashMap -> HashMap 33 | let mut dict = HashMap::new(); 34 | dict.insert(String::from("first_key"), (String::from("first_key"), true)); 35 | let answ = basics.dict2(dict); 36 | let mut cmp = HashMap::new(); 37 | cmp.insert(String::from("first_key"), (String::from("first_key"), true)); 38 | assert_eq!(answ, cmp); 39 | 40 | // (c_long, (c_double, bool)) -> (c_long, (String, bool), c_double) 41 | let arg = (1, (0.5, true)); 42 | let answ = basics.cmpd_tuple(arg); 43 | assert_eq!((1, (String::from("passed"), true), 0.0), answ); 44 | 45 | // Vec<((String, bool), PyObject)> -> Vec<(c_long, bool)> 46 | let a1: PyLong = 0.to_py_object(py); 47 | let a2: PyString = "str".to_py_object(py); 48 | let arg: Vec<((String, bool), PyObject)> = 49 | vec![((String::from("first"), true), a1.into_object()), 50 | ((String::from("second"), true), a2.into_object())]; 51 | let answ = basics.cmpd_list_and_tuple(arg); 52 | assert_eq!(answ, vec![(0, false), (1, true)]); 53 | 54 | // Vec<(c_long, bool)>, Vec -> Vec<(Vec, c_double)> 55 | let arg1 = vec![(1, true), (0, false)]; 56 | let arg2 = vec![0]; 57 | let answ = basics.cmpd_list(arg1, arg2); 58 | let cmp = vec![(vec![1], 1.0)]; 59 | assert_eq!(cmp, answ); 60 | 61 | // -> HashMap> 62 | let answ = basics.cmpd_dict(); 63 | let mut d1 = HashMap::new(); 64 | let mut d2 = HashMap::new(); 65 | d2.insert(0, (String::from("passed"), true)); 66 | d1.insert(String::from("passed"), d2); 67 | assert_eq!(answ, d1); 68 | 69 | // -> Vec> 70 | let answ = basics.cmpd_list_and_dict(); 71 | let mut d1 = HashMap::new(); 72 | d1.insert(0, (String::from("passed"), true)); 73 | let l1 = vec![d1]; 74 | assert_eq!(answ, l1); 75 | 76 | // -> HashMap> 77 | let answ = basics.cmpd_dict_and_ls(); 78 | let mut d1 = HashMap::new(); 79 | d1.insert(0, vec![0.0, 1.0, 2.0, 3.0]); 80 | assert_eq!(answ, d1); 81 | 82 | // Generics: 83 | let arg: PyLong = 0.to_py_object(py); 84 | let answ = basics.generic1(arg.into_object()); 85 | assert_eq!(0, answ.extract::(py).unwrap()); 86 | 87 | /* 88 | let a: PyLong = 0.to_py_object(py); 89 | let b: PyString = "second".to_py_object(py); 90 | let arg: Vec = vec![a.into_object(), b.into_object()]; 91 | let answ = basics.generic2(arg); 92 | assert_eq!(answ.len(), 1); 93 | assert_eq!(&answ.get(0).unwrap().extract::(py).unwrap(), 94 | "success"); 95 | */ 96 | } 97 | -------------------------------------------------------------------------------- /tests/py_test_lib/primitives.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | extern crate cpython; 3 | extern crate rustypy; 4 | 5 | use cpython::Python; 6 | 7 | #[test] 8 | fn primitives() { 9 | use test_package::rustypy_pybind::PyModules; 10 | let gil = Python::acquire_gil(); 11 | let py = gil.python(); 12 | 13 | let module: PyModules = PyModules::new(&py); 14 | let basics = module.basics.primitives; 15 | 16 | let arg = 1; 17 | let answ = basics.rust_bind_int_func(arg); 18 | assert_eq!(2, answ); 19 | 20 | let arg = 0.5; 21 | let answ = basics.rust_bind_float_func(arg); 22 | assert_eq!(1.0, answ); 23 | 24 | let arg = String::from("String from Rust, "); 25 | let answ = basics.rust_bind_str_func(arg); 26 | assert_eq!(String::from("String from Rust, added this in Python!"), 27 | answ); 28 | 29 | let arg = true; 30 | let answ = basics.rust_bind_bool_func(arg); 31 | assert_eq!(false, answ); 32 | 33 | let arg1 = String::from("String from Rust, "); 34 | let arg2 = 10; 35 | let answ = basics.other_prefix_tuple1((arg1, arg2)); 36 | assert_eq!((String::from("String from Rust, added this in Python!"), 20), 37 | answ); 38 | 39 | let arg1 = String::from("String from Rust, "); 40 | let arg2 = true; 41 | let answ = basics.other_prefix_tuple2((arg1, arg2)); 42 | assert_eq!((String::from("String from Rust, added this in Python!"), false), 43 | answ); 44 | 45 | let answ = basics.other_prefix_tuple3(0.5, true); 46 | assert_eq!((1.0, false), answ); 47 | } 48 | -------------------------------------------------------------------------------- /tests/py_test_lib/submodules.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | extern crate libc; 4 | extern crate cpython; 5 | extern crate rustypy; 6 | 7 | use cpython::{Python}; 8 | 9 | #[test] 10 | fn submodules() { 11 | use test_package::rustypy_pybind::PyModules; 12 | 13 | let gil = Python::acquire_gil(); 14 | let py = gil.python(); 15 | let test_package: PyModules = PyModules::new(&py); 16 | test_package.root_module_1.root_module_1(); 17 | test_package.root_module_2.root_module_2(); 18 | test_package.firstdir.call_from_first.first_module(); 19 | test_package.firstdir.subfirstdir.call_from_subfirst.subfirst_module(); 20 | } 21 | -------------------------------------------------------------------------------- /tests/py_test_lib/test_package/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iduartgomez/rustypy/b6d0f5ba7e4961bed75f454a21dd3ce25243faee/tests/py_test_lib/test_package/__init__.py -------------------------------------------------------------------------------- /tests/py_test_lib/test_package/basics/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iduartgomez/rustypy/b6d0f5ba7e4961bed75f454a21dd3ce25243faee/tests/py_test_lib/test_package/basics/__init__.py -------------------------------------------------------------------------------- /tests/py_test_lib/test_package/basics/nested_types.py: -------------------------------------------------------------------------------- 1 | import typing 2 | from rustypy.pywrapper import rust_bind 3 | from rustypy.rswrapper import Tuple, List, Dict 4 | 5 | # generics: 6 | A = typing.TypeVar('A', int, str) 7 | T = typing.Generic[A] 8 | 9 | 10 | @rust_bind 11 | def generic1(g_arg: T) -> T: 12 | assert isinstance(g_arg, int) or isinstance(g_arg, str), \ 13 | 'provided argument is not an int or str' 14 | return g_arg 15 | 16 | 17 | """ 18 | @rust_bind 19 | def generic2(g_arg: List[T]) -> List[T]: 20 | if g_arg[0] != 0: 21 | raise AssertionError 22 | if g_arg[1] != 'second': 23 | raise AssertionError 24 | out = ['success'] 25 | return out 26 | """ 27 | 28 | 29 | # containers/mappings: 30 | 31 | 32 | @rust_bind 33 | def dict1(dict_arg: Dict[str, int]) \ 34 | -> Dict[str, int]: 35 | for k, v in dict_arg.items(): 36 | dict_arg[k] = v + 1 37 | return dict_arg 38 | 39 | 40 | K = Tuple[str, bool] 41 | U = Dict[str, K] 42 | 43 | 44 | @rust_bind 45 | def dict2(dict_arg: U) -> U: 46 | return dict_arg 47 | 48 | 49 | J = Tuple[float, bool] 50 | U = List[J] 51 | 52 | 53 | @rust_bind 54 | def list1(ls_arg: U) \ 55 | -> List[str]: 56 | for e in ls_arg: 57 | if not isinstance(e[0], float): 58 | raise AssertionError 59 | if not isinstance(e[1], bool): 60 | raise AssertionError 61 | out_ls = ['passed'] 62 | return out_ls 63 | 64 | 65 | # nested types: 66 | 67 | 68 | @rust_bind 69 | def cmpd_tuple(tup_arg1: Tuple[int, J]) -> Tuple[int, K, float]: 70 | out = (1, ('passed', True), 0.0) 71 | return out 72 | 73 | 74 | X = List[Tuple[K, T]] 75 | U = List[Tuple[int, bool]] 76 | 77 | 78 | @rust_bind 79 | def cmpd_list_and_tuple(ls_arg: X) -> U: 80 | out = [] 81 | for i, e in enumerate(ls_arg): 82 | if not isinstance(e, tuple): 83 | raise AssertionError("list value not a tuple") 84 | if isinstance(e[1], str): 85 | out.append((i, True)) 86 | elif isinstance(e[1], int): 87 | out.append((i, False)) 88 | else: 89 | raise AssertionError("value is neither a string or an integer") 90 | return out 91 | 92 | 93 | U = List[Tuple[int, bool]] 94 | 95 | 96 | @rust_bind 97 | def cmpd_list(arg1: U, arg2: List[int]) \ 98 | -> List[Tuple[List[int], float]]: 99 | for e in arg1: 100 | assert isinstance(e[0], int) 101 | assert isinstance(e[1], bool) 102 | for e in arg2: 103 | assert isinstance(e, int) 104 | out = [([1], 1.0)] 105 | return out 106 | 107 | 108 | U = Dict[int, K] 109 | 110 | 111 | @rust_bind 112 | def cmpd_dict() -> Dict[str, U]: 113 | d = {'passed': {0: ('passed', True)}} 114 | return d 115 | 116 | 117 | @rust_bind 118 | def cmpd_list_and_dict() -> List[U]: 119 | ls = [{0: ('passed', True)}] 120 | return ls 121 | 122 | 123 | @rust_bind 124 | def cmpd_dict_and_ls() -> Dict[int, List[float]]: 125 | d = {0: [0.0, 1.0, 2.0, 3.0]} 126 | return d 127 | 128 | 129 | # not supported yet: 130 | """ 131 | U = typing.Set[K] 132 | def rust_bind_set1(set_arg: typing.Set[K]) \ 133 | -> typing.Set[K]: 134 | return set_arg 135 | def rust_bind_set2(set_arg: U) -> U: 136 | return set_arg 137 | """ 138 | -------------------------------------------------------------------------------- /tests/py_test_lib/test_package/basics/primitives.py: -------------------------------------------------------------------------------- 1 | from rustypy.rswrapper import Tuple 2 | 3 | 4 | # primitives: 5 | 6 | 7 | def rust_bind_int_func(int_arg: int) -> int: 8 | rval = int_arg + 1 9 | return rval 10 | 11 | 12 | def rust_bind_float_func(float_arg: float) -> float: 13 | rval = float_arg + 0.5 14 | return rval 15 | 16 | 17 | def rust_bind_str_func(str_arg: str) -> str: 18 | rval = str_arg + "added this in Python!" 19 | return rval 20 | 21 | 22 | def rust_bind_bool_func(bool_arg: bool) -> bool: 23 | rval = False 24 | return rval 25 | 26 | 27 | def other_prefix_tuple1(tup_arg: Tuple[str, int]) -> Tuple[str, int]: 28 | output = list(tup_arg) 29 | output[0] += "added this in Python!" 30 | output[1] += 10 31 | output = tuple(output) 32 | return output 33 | 34 | 35 | K = Tuple[str, bool] 36 | 37 | 38 | def other_prefix_tuple2(tup_arg: K) -> K: 39 | output = list(tup_arg) 40 | output[0] += "added this in Python!" 41 | if not output[1]: 42 | raise AssertionError(output[1]) 43 | else: 44 | output[1] = False 45 | output = tuple(output) 46 | return output 47 | 48 | 49 | J = Tuple[float, bool] 50 | 51 | 52 | def other_prefix_tuple3(tup_arg1: float, tup_arg2: bool) -> J: 53 | out_arg1 = tup_arg1 + 0.5 54 | if not tup_arg2: 55 | raise AssertionError(tup_arg2) 56 | else: 57 | out_arg2 = False 58 | return out_arg1, out_arg2 59 | -------------------------------------------------------------------------------- /tests/py_test_lib/test_package/firstdir/call_from_first.py: -------------------------------------------------------------------------------- 1 | from rustypy.pywrapper import rust_bind 2 | 3 | 4 | @rust_bind 5 | def first_module() -> None: 6 | print('... called from first module') 7 | 8 | 9 | if __name__ == "__main__": 10 | first_module() 11 | -------------------------------------------------------------------------------- /tests/py_test_lib/test_package/firstdir/subfirstdir/call_from_subfirst.py: -------------------------------------------------------------------------------- 1 | from rustypy.pywrapper import rust_bind 2 | 3 | 4 | @rust_bind 5 | def subfirst_module() -> None: 6 | print('... called from first submodule') 7 | 8 | 9 | if __name__ == "__main__": 10 | subfirst_module() 11 | -------------------------------------------------------------------------------- /tests/py_test_lib/test_package/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod rustypy_pybind; 2 | -------------------------------------------------------------------------------- /tests/py_test_lib/test_package/root_module_1.py: -------------------------------------------------------------------------------- 1 | from rustypy.pywrapper import rust_bind 2 | 3 | @rust_bind 4 | def root_module_1() -> None: 5 | print('... called from root module 1') 6 | -------------------------------------------------------------------------------- /tests/py_test_lib/test_package/root_module_2.py: -------------------------------------------------------------------------------- 1 | from rustypy.pywrapper import rust_bind 2 | 3 | @rust_bind 4 | def root_module_2() -> None: 5 | print('... called from root module 2') 6 | -------------------------------------------------------------------------------- /tests/rs_test_lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test_lib_rs" 3 | version = "0.0.1" 4 | authors = ["IDG "] 5 | 6 | [dependencies] 7 | libc = "~0.2" 8 | rustypy = "0.1.17" 9 | 10 | [lib] 11 | name = "test_lib_rs" 12 | path = './lib.rs' 13 | crate-type = ["cdylib", "rlib"] 14 | -------------------------------------------------------------------------------- /tests/rs_test_lib/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | extern crate libc; 4 | 5 | #[macro_use] 6 | extern crate rustypy; 7 | 8 | pub mod nested; 9 | 10 | use std::collections::HashMap; 11 | 12 | use rustypy::{PyArg, PyBool, PyDict, PyList, PyString, PyTuple}; 13 | 14 | pub mod primitives { 15 | use super::*; 16 | 17 | #[no_mangle] 18 | pub extern "C" fn python_bind_int_generator() -> u32 { 19 | 1 20 | } 21 | 22 | #[no_mangle] 23 | pub extern "C" fn python_bind_int(num: u32) -> u32 { 24 | num + 1 25 | } 26 | 27 | #[no_mangle] 28 | pub extern "C" fn python_bind_ref_int(num: &mut u32) { 29 | *num += 1; 30 | } 31 | 32 | #[no_mangle] 33 | pub unsafe extern "C" fn python_bind_str(pystr: *mut PyString) -> *mut PyString { 34 | let mut string = PyString::from_ptr_to_string(pystr); 35 | assert_eq!(string, "From Python."); 36 | string.push_str(" Added in Rust."); 37 | 38 | PyString::from(string).into_raw() 39 | } 40 | 41 | #[no_mangle] 42 | pub unsafe extern "C" fn python_bind_bool(ptr: *mut PyBool) -> *mut PyBool { 43 | let bool_t = PyBool::from_ptr_into_bool(ptr); 44 | assert!(bool_t); 45 | PyBool::from(false).into_raw() 46 | } 47 | } 48 | 49 | #[no_mangle] 50 | pub extern "C" fn python_bind_int_tuple(e1: i32, e2: i32) -> *mut PyTuple { 51 | pytuple!(PyArg::I32(e1), PyArg::I32(e2)).into_raw() 52 | } 53 | 54 | #[no_mangle] 55 | pub unsafe extern "C" fn python_bind_str_tuple(e1: *mut PyString) -> *mut PyTuple { 56 | let s = PyString::from(PyString::from_ptr_to_string(e1)); 57 | 58 | pytuple!( 59 | PyArg::PyString(s), 60 | PyArg::PyString(PyString::from("from Rust")) 61 | ) 62 | .into_raw() 63 | } 64 | 65 | #[no_mangle] 66 | pub unsafe extern "C" fn python_bind_tuple_mixed( 67 | e1: i32, 68 | e2: *mut PyBool, 69 | e3: f32, 70 | e4: *mut PyString, 71 | ) -> *mut PyTuple { 72 | assert_eq!(PyBool::from_ptr(e2), true); 73 | let s = PyString::from(PyString::from_ptr_to_string(e4)); 74 | pytuple!( 75 | PyArg::I32(e1), 76 | PyArg::PyBool(PyBool::from(false)), 77 | PyArg::F32(e3), 78 | PyArg::PyString(s) 79 | ) 80 | .into_raw() 81 | } 82 | 83 | #[no_mangle] 84 | pub unsafe extern "C" fn python_bind_list1(list: *mut PyList) -> *mut PyList { 85 | let converted = unpack_pylist!(list; PyList{PyString => String}); 86 | assert_eq!(converted.len(), 3); 87 | for (i, e) in (&converted).iter().enumerate() { 88 | if i == 0 { 89 | assert_eq!(e, "Python"); 90 | } else if i == 1 { 91 | assert_eq!(e, "in"); 92 | } else if i == 2 { 93 | assert_eq!(e, "Rust"); 94 | } 95 | } 96 | let content = vec!["Rust", "in", "Python"]; 97 | let returnval = PyList::from(content); 98 | returnval.into_raw() 99 | } 100 | 101 | #[no_mangle] 102 | pub unsafe extern "C" fn other_prefix_dict(dict: *mut usize) -> *mut usize { 103 | let dict = PyDict::::from_ptr(dict); 104 | assert_eq!(dict.get(&0_u64), Some(&PyString::from("From"))); 105 | assert_eq!(dict.get(&1_u64), Some(&PyString::from("Python"))); 106 | let mut hm = HashMap::new(); 107 | hm.insert(0_i64, PyArg::PyString(PyString::from("Back"))); 108 | hm.insert(1_i64, PyArg::PyString(PyString::from("Rust"))); 109 | PyDict::from(hm).into_raw() 110 | } 111 | -------------------------------------------------------------------------------- /tests/rs_test_lib/nested.rs: -------------------------------------------------------------------------------- 1 | use rustypy::{PyArg, PyBool, PyList, PyTuple}; 2 | use std::iter::FromIterator; 3 | 4 | #[no_mangle] 5 | pub unsafe extern "C" fn python_bind_list2(list: *mut PyList) -> *mut PyList { 6 | let converted = unpack_pylist!(list; PyList{PyTuple{(I64, (F32, I64,),)}}); 7 | assert_eq!( 8 | vec![(50i64, (1.0f32, 30i64)), (25i64, (0.5f32, 40i64))], 9 | converted 10 | ); 11 | 12 | let v: Vec = vec![ 13 | pytuple!(PyArg::F64(0.5f64), PyArg::PyBool(PyBool::from(true))), 14 | pytuple!(PyArg::F64(-0.5f64), PyArg::PyBool(PyBool::from(false))), 15 | ]; 16 | PyList::from_iter(v).into_raw() 17 | } 18 | 19 | #[no_mangle] 20 | pub unsafe extern "C" fn python_bind_nested1_t_n_ls(list: *mut PyList) -> *mut PyList { 21 | let converted = unpack_pylist!(list; PyList{PyList{PyTuple{(I64, (F32, I64,),)}}}); 22 | assert_eq!( 23 | vec![ 24 | vec![(50i64, (1.0f32, 30i64))], 25 | vec![(25i64, (0.5f32, 40i64))], 26 | ], 27 | converted 28 | ); 29 | let mut v0 = Vec::new(); 30 | for x in converted { 31 | let mut v1 = Vec::new(); 32 | for (f1, (f2, f3)) in x { 33 | let t_e = pytuple!( 34 | PyArg::I64(f1), 35 | PyArg::PyTuple(pytuple!(PyArg::F32(f2), PyArg::I64(f3)).into_raw()) 36 | ); 37 | v1.push(t_e); 38 | } 39 | v0.push(v1); 40 | } 41 | PyList::from_iter(v0).into_raw() 42 | } 43 | 44 | #[no_mangle] 45 | pub unsafe extern "C" fn python_bind_nested2_t_n_ls(list: *mut PyList) -> *mut PyList { 46 | let mut unpacked = unpack_pylist!(list; PyList{PyTuple{({PyList{I64 => i64}}, F32,)}}); 47 | assert_eq!(vec![(vec![1, 2, 3], 0.1), (vec![3, 2, 1], 0.2)], unpacked); 48 | unpacked.swap(0, 1); 49 | let mut v0 = Vec::new(); 50 | for (f1, f2) in unpacked { 51 | let e = pytuple!( 52 | PyArg::PyList(PyList::from_iter(f1).into_raw()), 53 | PyArg::F32(f2) 54 | ); 55 | v0.push(e); 56 | } 57 | PyList::from_iter(v0).into_raw() 58 | } 59 | -------------------------------------------------------------------------------- /tests/test_py_to_rs.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pathlib 3 | import subprocess 4 | import sys 5 | import unittest 6 | 7 | from rustypy.pywrapper import RustFuncGen 8 | 9 | _test_lib_dir = None 10 | _rs_lib_path = None 11 | 12 | 13 | def setUpModule(): 14 | # from rustypy.rswrapper.ffi_defs import _load_rust_lib 15 | # _load_rust_lib(recmpl=True) # uncomment to recompile rust lib 16 | py_test_dir = os.path.abspath(os.path.dirname(__file__)) 17 | global _test_lib_dir 18 | _test_lib_dir = pathlib.Path(py_test_dir, 'py_test_lib') 19 | global _rs_lib_path 20 | _rs_lib_path = pathlib.Path(py_test_dir).parent.joinpath('src', 'librustypy') 21 | 22 | # set python path 23 | mod_path = _test_lib_dir 24 | sys.path.append(str(mod_path)) 25 | # set env python path 26 | original_env = os.getenv('PYTHONPATH') 27 | if original_env: 28 | new_env = original_env + os.pathsep + str(mod_path) 29 | else: 30 | new_env = str(mod_path) 31 | os.putenv('PYTHONPATH', new_env) 32 | 33 | 34 | class GeneratePythonToRustBinds(unittest.TestCase): 35 | 36 | @classmethod 37 | def setUpClass(cls): 38 | prefixes = ["rust_bind_", "other_prefix_"] 39 | RustFuncGen(with_path=_test_lib_dir.joinpath("test_package"), 40 | prefixes=prefixes) 41 | 42 | def test_basics_primitives(self): 43 | p = subprocess.run(['cargo', 'test', 'primitives'], 44 | cwd=str(_test_lib_dir)) 45 | self.assertEqual(p.returncode, 0, 46 | 'failed Rust integration test `basics_primitives`') 47 | 48 | def test_basics_nested_types(self): 49 | p = subprocess.run(['cargo', 'test', 'nested_types'], 50 | cwd=str(_test_lib_dir)) 51 | self.assertEqual(p.returncode, 0, 52 | 'failed Rust integration test `basics_nested_types`') 53 | 54 | def test_nested_modules(self): 55 | p = subprocess.run(['cargo', 'test', 'submodules'], 56 | cwd=str(_test_lib_dir)) 57 | self.assertEqual(p.returncode, 0, 58 | 'failed Rust integration test `nested modules`') 59 | 60 | 61 | if __name__ == "__main__": 62 | unittest.main() 63 | -------------------------------------------------------------------------------- /tests/test_rs_to_py.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import sys 4 | import typing 5 | import unittest 6 | 7 | from rustypy.rswrapper import Float, Double, Tuple 8 | 9 | lib_test_entry = None 10 | lib_test = None 11 | 12 | 13 | def setUpModule(): 14 | # from rustypy.rswrapper.ffi_defs import _load_rust_lib 15 | # _load_rust_lib(recmpl=True) # uncomment to recompile rust lib 16 | py_test_dir = os.path.abspath(os.path.dirname(__file__)) 17 | _rs_lib_dir = os.path.join(os.path.dirname(py_test_dir), 'src', 'librustypy') 18 | try: 19 | os.remove(os.path.join(_rs_lib_dir, 'librustypy.so')) 20 | except: 21 | print("Library wasn't compiled") 22 | # load sample lib 23 | ext = {'darwin': '.dylib', 'win32': '.dll'}.get(sys.platform, '.so') 24 | pre = {'win32': ''}.get(sys.platform, 'lib') 25 | global lib_test_entry 26 | global lib_test 27 | lib_test_entry = os.path.join(py_test_dir, 'rs_test_lib') 28 | lib_test = os.path.join(lib_test_entry, 'target', 'debug', 29 | '{}test_lib_rs{}'.format(pre, ext)) 30 | subprocess.run(['cargo', 'build'], cwd=str(lib_test_entry)).check_returncode() 31 | 32 | 33 | class GenerateRustToPythonBinds(unittest.TestCase): 34 | 35 | @classmethod 36 | def setUpClass(cls): 37 | from rustypy.rswrapper import bind_rs_crate_funcs 38 | 39 | prefixes = ["python_bind_", "other_prefix_"] 40 | cls.bindings = bind_rs_crate_funcs(lib_test_entry, lib_test, prefixes) 41 | 42 | def test_basics_primitives(self): 43 | # non ref int 44 | return_val = self.bindings.python_bind_int(1) 45 | self.assertIsInstance(return_val, int) 46 | self.assertEqual(return_val, 2) 47 | # no param fn call 48 | return_val = self.bindings.python_bind_int_generator() 49 | self.assertIsInstance(return_val, int) 50 | self.assertEqual(return_val, 1) 51 | # ref int 52 | _, refs = self.bindings.python_bind_ref_int( 53 | 1, return_ref=True, get_contents=True) 54 | self.assertEqual(refs[0], 2) 55 | # string 56 | return_val = self.bindings.python_bind_str("From Python.") 57 | self.assertEqual(return_val, "From Python. Added in Rust.") 58 | # bool 59 | return_val = self.bindings.python_bind_bool(True) 60 | self.assertEqual(return_val, False) 61 | 62 | def test_tuple_conversion(self): 63 | # tuple 64 | U = Tuple[int, int] 65 | self.bindings.python_bind_int_tuple.restype = U 66 | for i in range(0, 100): 67 | return_val = self.bindings.python_bind_int_tuple(1, 2) 68 | self.assertEqual(return_val, (1, 2)) 69 | 70 | U = Tuple[str, str] 71 | self.bindings.python_bind_str_tuple.restype = U 72 | return_val = self.bindings.python_bind_str_tuple("Some") 73 | self.assertEqual(return_val, ("Some", "from Rust")) 74 | 75 | # mixed types 76 | T = Tuple[int, bool, Float, str] 77 | self.bindings.python_bind_tuple_mixed.restype = T 78 | return_val = self.bindings.python_bind_tuple_mixed( 79 | 1, True, 2.5, "Some from Rust") 80 | self.assertEqual(return_val, (1, False, 2.5, "Some from Rust")) 81 | 82 | def test_list_conversion(self): 83 | # string list 84 | T = typing.List[str] 85 | self.bindings.python_bind_list1.add_argtype(0, T) 86 | self.bindings.python_bind_list1.restype = T 87 | result = self.bindings.python_bind_list1(["Python", "in", "Rust"]) 88 | self.assertEqual(result, ["Rust", "in", "Python"]) 89 | 90 | # list of tuples 91 | T = typing.List[Tuple[int, Tuple[Float, int]]] 92 | self.bindings.python_bind_list2.add_argtype(0, T) 93 | U = typing.List[Tuple[Double, bool]] 94 | self.bindings.python_bind_list2.restype = U 95 | result = self.bindings.python_bind_list2( 96 | [(50, (1.0, 30)), (25, (0.5, 40))]) 97 | self.assertEqual(result, [(0.5, True), (-0.5, False)]) 98 | 99 | # list of lists of tuples 100 | T = typing.List[typing.List[ 101 | Tuple[int, Tuple[Float, int]]]] 102 | self.bindings.python_bind_nested1_t_n_ls.add_argtype(0, T) 103 | self.bindings.python_bind_nested1_t_n_ls.restype = T 104 | result = self.bindings.python_bind_nested1_t_n_ls( 105 | [[(50, (1.0, 30))], [(25, (0.5, 40))]]) 106 | self.assertEqual(result, [[(50, (1.0, 30))], [(25, (0.5, 40))]]) 107 | 108 | # list of tuples of lists 109 | T = typing.List[Tuple[typing.List[int], Float]] 110 | self.bindings.python_bind_nested2_t_n_ls.add_argtype(0, T) 111 | self.bindings.python_bind_nested2_t_n_ls.restype = T 112 | result = self.bindings.python_bind_nested2_t_n_ls( 113 | [([1, 2, 3], 0.1), ([3, 2, 1], 0.2)]) 114 | f = [] 115 | for x in result: 116 | l = [] 117 | for i, y in enumerate(x): 118 | if isinstance(y, float): 119 | e = round(y, 1) 120 | else: 121 | e = y 122 | l.append(e) 123 | f.append(tuple(l)) 124 | self.assertEqual(f, [([3, 2, 1], 0.2), ([1, 2, 3], 0.1)]) 125 | 126 | def test_dict_conversion(self): 127 | from rustypy.rswrapper import HashableType 128 | d = {0: "From", 1: "Python"} 129 | T = typing.Dict[HashableType('u64'), str] 130 | R = typing.Dict[HashableType('i64'), str] 131 | self.bindings.other_prefix_dict.add_argtype(0, T) 132 | self.bindings.other_prefix_dict.restype = R 133 | result = self.bindings.other_prefix_dict(d) 134 | self.assertEqual(result, {0: "Back", 1: "Rust"}) 135 | 136 | 137 | if __name__ == "__main__": 138 | unittest.main() 139 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py35, py36, py37 3 | 4 | [testenv] 5 | deps = pytest 6 | commands = 7 | pytest 8 | --------------------------------------------------------------------------------