From e70fb0446d2f17d4511658f069028f1a89198f2b Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 28 Nov 2025 10:14:59 +0900 Subject: [PATCH 1/3] rustpython-cpython --- Cargo.lock | 10 ++ Cargo.toml | 3 + crates/cpython/Cargo.toml | 17 +++ crates/cpython/src/lib.rs | 304 ++++++++++++++++++++++++++++++++++++++ src/lib.rs | 5 + 5 files changed, 339 insertions(+) create mode 100644 crates/cpython/Cargo.toml create mode 100644 crates/cpython/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index f96b527dfe..450fbe4701 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2943,6 +2943,7 @@ dependencies = [ "pyo3", "ruff_python_parser", "rustpython-compiler", + "rustpython-cpython", "rustpython-pylib", "rustpython-stdlib", "rustpython-vm", @@ -3035,6 +3036,15 @@ dependencies = [ "ruff_text_size", ] +[[package]] +name = "rustpython-cpython" +version = "0.4.0" +dependencies = [ + "pyo3", + "rustpython-derive", + "rustpython-vm", +] + [[package]] name = "rustpython-derive" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 8993ce145f..baad9bf374 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ ssl-rustls = ["ssl", "rustpython-stdlib/ssl-rustls"] ssl-openssl = ["ssl", "rustpython-stdlib/ssl-openssl"] ssl-vendor = ["ssl-openssl", "rustpython-stdlib/ssl-vendor"] tkinter = ["rustpython-stdlib/tkinter"] +cpython = ["dep:rustpython-cpython"] [build-dependencies] winresource = "0.1" @@ -34,6 +35,7 @@ rustpython-compiler = { workspace = true } rustpython-pylib = { workspace = true, optional = true } rustpython-stdlib = { workspace = true, optional = true, features = ["compiler"] } rustpython-vm = { workspace = true, features = ["compiler"] } +rustpython-cpython = { workspace = true, optional = true } ruff_python_parser = { workspace = true } cfg-if = { workspace = true } @@ -150,6 +152,7 @@ rustpython-stdlib = { path = "crates/stdlib", default-features = false, version rustpython-sre_engine = { path = "crates/sre_engine", version = "0.4.0" } rustpython-wtf8 = { path = "crates/wtf8", version = "0.4.0" } rustpython-doc = { path = "crates/doc", version = "0.4.0" } +rustpython-cpython = { path = "crates/cpython", version = "0.4.0" } ruff_python_parser = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } ruff_python_ast = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } diff --git a/crates/cpython/Cargo.toml b/crates/cpython/Cargo.toml new file mode 100644 index 0000000000..67d7478b8c --- /dev/null +++ b/crates/cpython/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "rustpython-cpython" +description = "RustPython to CPython bridge via PyO3" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +repository.workspace = true +license.workspace = true + +[dependencies] +rustpython-vm = { workspace = true } +rustpython-derive = { workspace = true } +pyo3 = { version = "0.26", features = ["auto-initialize"] } + +[lints] +workspace = true diff --git a/crates/cpython/src/lib.rs b/crates/cpython/src/lib.rs new file mode 100644 index 0000000000..361903be16 --- /dev/null +++ b/crates/cpython/src/lib.rs @@ -0,0 +1,304 @@ +//! RustPython to CPython bridge via PyO3 +//! +//! This crate provides interoperability between RustPython and CPython, +//! allowing RustPython code to execute functions in the CPython runtime. +//! +//! # Background +//! +//! RustPython does not implement all CPython C extension modules. +//! This crate enables calling into the real CPython runtime for functionality +//! that is not yet available in RustPython. +//! +//! # Architecture +//! +//! Communication between RustPython and CPython uses PyO3 for in-process calls. +//! Data is serialized using Python's `pickle` protocol: +//! +//! ```text +//! RustPython CPython +//! │ │ +//! │ pickle.dumps(args, kwargs) │ +//! │ ──────────────────────────────► │ +//! │ │ exec(source) +//! │ │ result = func(*args, **kwargs) +//! │ pickle.dumps(result) │ +//! │ ◄────────────────────────────── │ +//! │ │ +//! │ pickle.loads(result) │ +//! ``` +//! +//! # Limitations +//! +//! - **File-based functions only**: Functions defined in REPL or via `exec()` will fail +//! (`inspect.getsource()` requires source file access) +//! - **Picklable data only**: Cannot pass functions, classes, file handles, etc. +//! - **Performance overhead**: pickle serialization + CPython GIL acquisition +//! - **CPython required**: System must have CPython installed (linked via PyO3) + +#[macro_use] +extern crate rustpython_derive; + +use rustpython_vm::{PyRef, VirtualMachine, builtins::PyModule}; + +/// Create the _cpython module +pub fn make_module(vm: &VirtualMachine) -> PyRef { + _cpython::make_module(vm) +} + +#[pymodule] +mod _cpython { + use pyo3::PyErr; + use pyo3::prelude::PyAnyMethods; + use pyo3::types::PyBytes as Pyo3Bytes; + use pyo3::types::PyBytesMethods; + use pyo3::types::PyDictMethods; + use rustpython_vm::{ + Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, + builtins::{PyBytes as RustPyBytes, PyBytesRef, PyDict, PyStrRef, PyTypeRef}, + function::FuncArgs, + types::{Callable, Constructor, Representable}, + }; + + /// Wrapper class for executing functions in CPython. + /// Used as a decorator: @_cpython.call + #[pyattr] + #[pyclass(name = "call")] + #[derive(Debug, PyPayload)] + struct CPythonCall { + source: String, + func_name: String, + } + + impl Constructor for CPythonCall { + type Args = PyObjectRef; + + fn py_new(cls: PyTypeRef, func: Self::Args, vm: &VirtualMachine) -> PyResult { + // Get function name + let func_name = func + .get_attr("__name__", vm)? + .downcast::() + .map_err(|_| vm.new_type_error("function must have __name__".to_owned()))? + .as_str() + .to_owned(); + + // Get source using inspect.getsource(func) + let inspect = vm.import("inspect", 0)?; + let getsource = inspect.get_attr("getsource", vm)?; + let source_obj = getsource.call((func.clone(),), vm)?; + let source_full = source_obj + .downcast::() + .map_err(|_| vm.new_type_error("getsource did not return str".to_owned()))? + .as_str() + .to_owned(); + + // Strip decorator lines from source (lines starting with @) + // Find the first line that starts with 'def ' or 'async def ' + let source = strip_decorators(&source_full); + + Self { source, func_name } + .into_ref_with_type(vm, cls) + .map(Into::into) + } + } + + /// Strip decorator lines from function source code. + /// Returns source starting from 'def' or 'async def'. + fn strip_decorators(source: &str) -> String { + let lines = source.lines(); + let mut result_lines = Vec::new(); + let mut found_def = false; + + for line in lines { + let trimmed = line.trim_start(); + if !found_def { + if trimmed.starts_with("def ") || trimmed.starts_with("async def ") { + found_def = true; + result_lines.push(line); + } + // Skip decorator lines (starting with @) and blank lines before def + } else { + result_lines.push(line); + } + } + + result_lines.join("\n") + } + + impl Callable for CPythonCall { + type Args = FuncArgs; + + fn call(zelf: &Py, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + // Import pickle module + let pickle = vm.import("pickle", 0)?; + let dumps = pickle.get_attr("dumps", vm)?; + let loads = pickle.get_attr("loads", vm)?; + + // Pickle args and kwargs + let args_tuple = vm.ctx.new_tuple(args.args); + let kwargs_dict = PyDict::default().into_ref(&vm.ctx); + for (key, value) in args.kwargs { + kwargs_dict.set_item(&key, value, vm)?; + } + + let pickled_args = dumps.call((args_tuple,), vm)?; + let pickled_kwargs = dumps.call((kwargs_dict,), vm)?; + + let pickled_args_bytes = pickled_args + .downcast::() + .map_err(|_| vm.new_type_error("pickle.dumps did not return bytes".to_owned()))?; + let pickled_kwargs_bytes = pickled_kwargs + .downcast::() + .map_err(|_| vm.new_type_error("pickle.dumps did not return bytes".to_owned()))?; + + // Call execute_impl() + let result_bytes = execute_impl( + &zelf.source, + &zelf.func_name, + pickled_args_bytes.as_bytes(), + pickled_kwargs_bytes.as_bytes(), + vm, + )?; + + // Unpickle result + let result_py_bytes = RustPyBytes::from(result_bytes).into_ref(&vm.ctx); + loads.call((result_py_bytes,), vm) + } + } + + impl Representable for CPythonCall { + fn repr_str(zelf: &Py, _vm: &VirtualMachine) -> PyResult { + Ok(format!("<_cpython.call wrapper for '{}'>", zelf.func_name)) + } + } + + #[pyclass(with(Constructor, Callable, Representable))] + impl CPythonCall {} + + /// Internal implementation for executing Python code in CPython. + fn execute_impl( + source: &str, + func_name: &str, + args_bytes: &[u8], + kwargs_bytes: &[u8], + vm: &VirtualMachine, + ) -> PyResult> { + // Build the CPython code to execute + let cpython_code = format!( + r#" +import pickle as __pickle + +# Unpickle arguments +__args__ = __pickle.loads(__pickled_args__) +__kwargs__ = __pickle.loads(__pickled_kwargs__) +# Execute the source code (defines the function) +{source} + +# Call the function and pickle the result +__result__ = {func_name}(*__args__, **__kwargs__) +__pickled_result__ = __pickle.dumps(__result__, protocol=4) +"#, + source = source, + func_name = func_name, + ); + + // Execute in CPython via PyO3 + pyo3::Python::attach(|py| -> Result, PyErr> { + // Create Python bytes for pickled data + let py_args = Pyo3Bytes::new(py, args_bytes); + let py_kwargs = Pyo3Bytes::new(py, kwargs_bytes); + + // Create globals dict with pickled args + let globals = pyo3::types::PyDict::new(py); + globals.set_item("__pickled_args__", &py_args)?; + globals.set_item("__pickled_kwargs__", &py_kwargs)?; + + // Execute using compile + exec pattern + let builtins = py.import("builtins")?; + let compile = builtins.getattr("compile")?; + let exec_fn = builtins.getattr("exec")?; + + // Compile the code + let code = compile.call1((&cpython_code, "", "exec"))?; + + // Execute with globals + exec_fn.call1((code, &globals))?; + + // Get the pickled result + let result = globals.get_item("__pickled_result__")?.ok_or_else(|| { + PyErr::new::("No result returned") + })?; + let result_bytes: &pyo3::Bound<'_, Pyo3Bytes> = result.downcast()?; + Ok(result_bytes.as_bytes().to_vec()) + }) + .map_err(|e| vm.new_runtime_error(format!("CPython error: {}", e))) + } + + /// Execute a Python function in CPython runtime. + /// + /// # Arguments + /// * `source` - The complete source code of the function + /// * `func_name` - The name of the function to call + /// * `pickled_args` - Pickled positional arguments (bytes) + /// * `pickled_kwargs` - Pickled keyword arguments (bytes) + /// + /// # Returns + /// Pickled result from CPython (bytes) + #[pyfunction] + fn execute( + source: PyStrRef, + func_name: PyStrRef, + pickled_args: PyBytesRef, + pickled_kwargs: PyBytesRef, + vm: &VirtualMachine, + ) -> PyResult { + let result_bytes = execute_impl( + source.as_str(), + func_name.as_str(), + pickled_args.as_bytes(), + pickled_kwargs.as_bytes(), + vm, + )?; + Ok(RustPyBytes::from(result_bytes).into_ref(&vm.ctx)) + } + + /// Execute arbitrary Python code in CPython and return pickled result. + /// + /// # Arguments + /// * `code` - Python code to execute (should assign result to `__result__`) + /// + /// # Returns + /// Pickled result from CPython (bytes) + #[pyfunction] + fn eval_code(code: PyStrRef, vm: &VirtualMachine) -> PyResult { + let code_str = code.as_str(); + + let wrapper_code = format!( + r#" +import pickle +{code} +__pickled_result__ = pickle.dumps(__result__, protocol=4) +"#, + code = code_str, + ); + + let result_bytes = pyo3::Python::attach(|py| -> Result, PyErr> { + let globals = pyo3::types::PyDict::new(py); + + let builtins = py.import("builtins")?; + let compile = builtins.getattr("compile")?; + let exec_fn = builtins.getattr("exec")?; + + let code = compile.call1((&wrapper_code, "", "exec"))?; + exec_fn.call1((code, &globals))?; + + let result = globals.get_item("__pickled_result__")?.ok_or_else(|| { + PyErr::new::("No __result__ defined in code") + })?; + let result_bytes: &pyo3::Bound<'_, Pyo3Bytes> = result.downcast()?; + Ok(result_bytes.as_bytes().to_vec()) + }) + .map_err(|e| vm.new_runtime_error(format!("CPython error: {}", e)))?; + + Ok(RustPyBytes::from(result_bytes).into_ref(&vm.ctx)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 976d0bc0a3..52703018f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,6 +108,11 @@ pub fn run(init: impl FnOnce(&mut VirtualMachine) + 'static) -> ExitCode { } config = config.init_hook(Box::new(init)); + #[cfg(feature = "cpython")] + { + config = config.add_native_module("_cpython".to_owned(), rustpython_cpython::make_module); + } + let interp = config.interpreter(); let exitcode = interp.run(move |vm| run_rustpython(vm, run_mode)); From 7a540436e3961c00c2f22819a612401a5d89086d Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 28 Nov 2025 15:59:13 +0900 Subject: [PATCH 2/3] cpython.import_module --- Cargo.lock | 1 + crates/cpython/Cargo.toml | 1 + crates/cpython/src/lib.rs | 563 ++++++++++++++++++++++++++++++++++-- extra_tests/test_cpython.py | 223 ++++++++++++++ 4 files changed, 757 insertions(+), 31 deletions(-) create mode 100644 extra_tests/test_cpython.py diff --git a/Cargo.lock b/Cargo.lock index 450fbe4701..b350b0d9ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3040,6 +3040,7 @@ dependencies = [ name = "rustpython-cpython" version = "0.4.0" dependencies = [ + "crossbeam-utils", "pyo3", "rustpython-derive", "rustpython-vm", diff --git a/crates/cpython/Cargo.toml b/crates/cpython/Cargo.toml index 67d7478b8c..4d4960baed 100644 --- a/crates/cpython/Cargo.toml +++ b/crates/cpython/Cargo.toml @@ -12,6 +12,7 @@ license.workspace = true rustpython-vm = { workspace = true } rustpython-derive = { workspace = true } pyo3 = { version = "0.26", features = ["auto-initialize"] } +crossbeam-utils = { workspace = true } [lints] workspace = true diff --git a/crates/cpython/src/lib.rs b/crates/cpython/src/lib.rs index 361903be16..aed92d352c 100644 --- a/crates/cpython/src/lib.rs +++ b/crates/cpython/src/lib.rs @@ -47,16 +47,21 @@ pub fn make_module(vm: &VirtualMachine) -> PyRef { #[pymodule] mod _cpython { + use crossbeam_utils::atomic::AtomicCell; use pyo3::PyErr; use pyo3::prelude::PyAnyMethods; use pyo3::types::PyBytes as Pyo3Bytes; use pyo3::types::PyBytesMethods; use pyo3::types::PyDictMethods; use rustpython_vm::{ - Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, - builtins::{PyBytes as RustPyBytes, PyBytesRef, PyDict, PyStrRef, PyTypeRef}, - function::FuncArgs, - types::{Callable, Constructor, Representable}, + Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, + builtins::{PyBytes as RustPyBytes, PyBytesRef, PyDict, PyStr, PyStrRef, PyTypeRef}, + function::{FuncArgs, PyArithmeticValue, PyComparisonValue}, + protocol::{PyMappingMethods, PyNumberMethods, PySequenceMethods}, + types::{ + AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, GetAttr, Iterable, + PyComparisonOp, Representable, + }, }; /// Wrapper class for executing functions in CPython. @@ -101,38 +106,75 @@ mod _cpython { } } - /// Strip decorator lines from function source code. - /// Returns source starting from 'def' or 'async def'. + /// Serialize a RustPython object to pickle bytes. + fn rustpython_pickle_dumps( + obj: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult> { + let pickle = vm.import("pickle", 0)?; + let dumps = pickle.get_attr("dumps", vm)?; + dumps + .call((obj,), vm)? + .downcast::() + .map_err(|_| vm.new_type_error("pickle.dumps did not return bytes".to_owned())) + } + + /// Deserialize pickle bytes to a RustPython object. + fn rustpython_pickle_loads(bytes: &[u8], vm: &VirtualMachine) -> PyResult { + let pickle = vm.import("pickle", 0)?; + let loads = pickle.get_attr("loads", vm)?; + let bytes_obj = RustPyBytes::from(bytes.to_vec()).into_ref(&vm.ctx); + loads.call((bytes_obj,), vm) + } + + /// Strip decorator lines from function source code and dedent. + /// Returns source starting from 'def' or 'async def', with common indentation removed. fn strip_decorators(source: &str) -> String { - let lines = source.lines(); + let lines: Vec<&str> = source.lines().collect(); let mut result_lines = Vec::new(); let mut found_def = false; + let mut base_indent = 0; - for line in lines { + for line in &lines { let trimmed = line.trim_start(); if !found_def { if trimmed.starts_with("def ") || trimmed.starts_with("async def ") { found_def = true; - result_lines.push(line); + // Calculate base indentation from the def line + base_indent = line.len() - trimmed.len(); + result_lines.push(*line); } // Skip decorator lines (starting with @) and blank lines before def } else { - result_lines.push(line); + result_lines.push(*line); } } - result_lines.join("\n") + // Dedent all lines by base_indent + result_lines + .iter() + .map(|line| { + if line.len() >= base_indent + && line + .chars() + .take(base_indent) + .all(|c| c == ' ' || c == '\t') + { + &line[base_indent..] + } else if line.trim().is_empty() { + "" + } else { + *line + } + }) + .collect::>() + .join("\n") } impl Callable for CPythonCall { type Args = FuncArgs; fn call(zelf: &Py, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - // Import pickle module - let pickle = vm.import("pickle", 0)?; - let dumps = pickle.get_attr("dumps", vm)?; - let loads = pickle.get_attr("loads", vm)?; - // Pickle args and kwargs let args_tuple = vm.ctx.new_tuple(args.args); let kwargs_dict = PyDict::default().into_ref(&vm.ctx); @@ -140,15 +182,8 @@ mod _cpython { kwargs_dict.set_item(&key, value, vm)?; } - let pickled_args = dumps.call((args_tuple,), vm)?; - let pickled_kwargs = dumps.call((kwargs_dict,), vm)?; - - let pickled_args_bytes = pickled_args - .downcast::() - .map_err(|_| vm.new_type_error("pickle.dumps did not return bytes".to_owned()))?; - let pickled_kwargs_bytes = pickled_kwargs - .downcast::() - .map_err(|_| vm.new_type_error("pickle.dumps did not return bytes".to_owned()))?; + let pickled_args_bytes = rustpython_pickle_dumps(args_tuple.into(), vm)?; + let pickled_kwargs_bytes = rustpython_pickle_dumps(kwargs_dict.into(), vm)?; // Call execute_impl() let result_bytes = execute_impl( @@ -160,8 +195,7 @@ mod _cpython { )?; // Unpickle result - let result_py_bytes = RustPyBytes::from(result_bytes).into_ref(&vm.ctx); - loads.call((result_py_bytes,), vm) + rustpython_pickle_loads(&result_bytes, vm) } } @@ -224,13 +258,14 @@ __pickled_result__ = __pickle.dumps(__result__, protocol=4) exec_fn.call1((code, &globals))?; // Get the pickled result - let result = globals.get_item("__pickled_result__")?.ok_or_else(|| { + let result = globals.get_item("__pickled_result__")?; + let result = result.ok_or_else(|| { PyErr::new::("No result returned") })?; let result_bytes: &pyo3::Bound<'_, Pyo3Bytes> = result.downcast()?; Ok(result_bytes.as_bytes().to_vec()) }) - .map_err(|e| vm.new_runtime_error(format!("CPython error: {}", e))) + .map_err(|e| vm.new_runtime_error(format!("CPython exec error: {}", e))) } /// Execute a Python function in CPython runtime. @@ -291,14 +326,480 @@ __pickled_result__ = pickle.dumps(__result__, protocol=4) let code = compile.call1((&wrapper_code, "", "exec"))?; exec_fn.call1((code, &globals))?; - let result = globals.get_item("__pickled_result__")?.ok_or_else(|| { + let result = globals.get_item("__pickled_result__")?; + let result = result.ok_or_else(|| { PyErr::new::("No __result__ defined in code") })?; let result_bytes: &pyo3::Bound<'_, Pyo3Bytes> = result.downcast()?; Ok(result_bytes.as_bytes().to_vec()) }) - .map_err(|e| vm.new_runtime_error(format!("CPython error: {}", e)))?; + .map_err(|e| vm.new_runtime_error(format!("CPython eval error: {}", e)))?; Ok(RustPyBytes::from(result_bytes).into_ref(&vm.ctx)) } + + /// Pickle a CPython object to bytes. + fn pickle_in_cpython( + py: pyo3::Python<'_>, + obj: &pyo3::Bound<'_, pyo3::PyAny>, + ) -> Result, PyErr> { + let pickle = py.import("pickle")?; + let pickled = pickle.call_method1("dumps", (obj, 4i32))?; + let bytes: &pyo3::Bound<'_, Pyo3Bytes> = pickled.downcast()?; + Ok(bytes.as_bytes().to_vec()) + } + + /// Unpickle bytes in CPython. + fn unpickle_in_cpython<'py>( + py: pyo3::Python<'py>, + bytes: &[u8], + ) -> Result, PyErr> { + let pickle = py.import("pickle")?; + pickle.call_method1("loads", (Pyo3Bytes::new(py, bytes),)) + } + + /// Create a CPythonObject from a pyo3 object, attempting to pickle it. + fn create_cpython_object( + py: pyo3::Python<'_>, + obj: &pyo3::Bound<'_, pyo3::PyAny>, + ) -> CPythonObject { + let pickled = pickle_in_cpython(py, obj).ok(); + CPythonObject { + py_obj: obj.clone().unbind(), + pickled, + } + } + + /// Convert a CPythonObject to RustPython object. + /// If pickled bytes exist, tries to unpickle to native RustPython object. + /// Falls back to returning the CPythonObject wrapper. + fn cpython_to_rustpython(cpython_obj: CPythonObject, vm: &VirtualMachine) -> PyResult { + if let Some(ref bytes) = cpython_obj.pickled { + if let Ok(unpickled) = rustpython_pickle_loads(bytes, vm) { + return Ok(unpickled); + } + // Unpickle failed (e.g., numpy arrays need numpy module) + // Fall through to return CPythonObject wrapper + } + Ok(cpython_obj.into_ref(&vm.ctx).into()) + } + + /// Get attribute from a CPython object + fn cpython_getattr_impl( + py_obj: &pyo3::Py, + name: &str, + vm: &VirtualMachine, + ) -> PyResult { + let cpython_obj = pyo3::Python::attach(|py| -> Result { + let obj = py_obj.bind(py); + let attr = obj.getattr(name)?; + Ok(create_cpython_object(py, &attr)) + }) + .map_err(|e| vm.new_attribute_error(format!("CPython getattr error: {}", e)))?; + + cpython_to_rustpython(cpython_obj, vm) + } + + /// Call a CPython object + fn cpython_call_impl( + py_obj: &pyo3::Py, + args: FuncArgs, + vm: &VirtualMachine, + ) -> PyResult { + // Pickle args and kwargs in RustPython + let args_tuple = vm.ctx.new_tuple(args.args); + let kwargs_dict = PyDict::default().into_ref(&vm.ctx); + for (key, value) in args.kwargs { + kwargs_dict.set_item(&key, value, vm)?; + } + + let args_bytes = rustpython_pickle_dumps(args_tuple.into(), vm)?; + let kwargs_bytes = rustpython_pickle_dumps(kwargs_dict.into(), vm)?; + + let cpython_obj = pyo3::Python::attach(|py| -> Result { + let obj = py_obj.bind(py); + + // Unpickle args/kwargs in CPython + let args_py = unpickle_in_cpython(py, args_bytes.as_bytes())?; + let kwargs_py = unpickle_in_cpython(py, kwargs_bytes.as_bytes())?; + + // Call the object + let call_result = obj.call(args_py.downcast()?, Some(kwargs_py.downcast()?))?; + + Ok(create_cpython_object(py, &call_result)) + }) + .map_err(|e| vm.new_runtime_error(format!("CPython call error: {}", e)))?; + + cpython_to_rustpython(cpython_obj, vm) + } + + /// Represents an object to be passed into CPython. + /// Either already a CPython object (Native) or pickled RustPython object (Pickled). + enum ToCPythonObject<'a> { + Native(&'a pyo3::Py), + Pickled(PyRef), + } + + impl ToCPythonObject<'_> { + fn to_pyo3<'py>( + &self, + py: pyo3::Python<'py>, + ) -> Result, PyErr> { + match self { + ToCPythonObject::Native(obj) => Ok(obj.bind(py).clone()), + ToCPythonObject::Pickled(bytes) => unpickle_in_cpython(py, bytes.as_bytes()), + } + } + } + + /// Convert a RustPython object to ToCPythonObject for passing into CPython + fn to_cpython_object<'a>( + obj: &'a PyObject, + vm: &VirtualMachine, + ) -> PyResult> { + if let Some(cpython_obj) = obj.downcast_ref::() { + Ok(ToCPythonObject::Native(&cpython_obj.py_obj)) + } else { + let pickled = rustpython_pickle_dumps(obj.to_owned(), vm)?; + Ok(ToCPythonObject::Pickled(pickled)) + } + } + + /// Execute binary operation on CPython objects + fn cpython_binary_op(a: &PyObject, b: &PyObject, op: &str, vm: &VirtualMachine) -> PyResult { + // If neither is CPythonObject, return NotImplemented + if a.downcast_ref::().is_none() + && b.downcast_ref::().is_none() + { + return Ok(vm.ctx.not_implemented()); + } + + let a_obj = to_cpython_object(a, vm)?; + let b_obj = to_cpython_object(b, vm)?; + + let result = + pyo3::Python::attach(|py| -> Result, PyErr> { + let a_py = a_obj.to_pyo3(py)?; + let b_py = b_obj.to_pyo3(py)?; + + let result_obj = a_py.call_method1(op, (&b_py,))?; + + if result_obj.is(&py.NotImplemented()) { + return Ok(PyArithmeticValue::NotImplemented); + } + + Ok(PyArithmeticValue::Implemented(create_cpython_object( + py, + &result_obj, + ))) + }) + .map_err(|e| vm.new_runtime_error(format!("CPython binary op error: {}", e)))?; + + match result { + PyArithmeticValue::NotImplemented => Ok(vm.ctx.not_implemented()), + PyArithmeticValue::Implemented(cpython_obj) => cpython_to_rustpython(cpython_obj, vm), + } + } + + /// Wrapper for CPython objects + #[pyattr] + #[pyclass(name = "Object")] + #[derive(PyPayload)] + struct CPythonObject { + py_obj: pyo3::Py, + /// Pickled bytes for potential unpickling to native RustPython object + pickled: Option>, + } + + impl std::fmt::Debug for CPythonObject { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CPythonObject") + .field("py_obj", &"") + .finish() + } + } + + impl GetAttr for CPythonObject { + fn getattro(zelf: &Py, name: &Py, vm: &VirtualMachine) -> PyResult { + cpython_getattr_impl(&zelf.py_obj, name.as_str(), vm) + } + } + + impl Callable for CPythonObject { + type Args = FuncArgs; + + fn call(zelf: &Py, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + cpython_call_impl(&zelf.py_obj, args, vm) + } + } + + impl Representable for CPythonObject { + fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { + // Get repr from CPython directly + let result = pyo3::Python::attach(|py| -> Result { + let obj = zelf.py_obj.bind(py); + let builtins = py.import("builtins")?; + let repr_fn = builtins.getattr("repr")?; + let repr_result = repr_fn.call1((obj,))?; + repr_result.extract() + }) + .map_err(|e| vm.new_runtime_error(format!("CPython repr error: {}", e)))?; + Ok(result) + } + } + + impl AsNumber for CPythonObject { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + add: Some(|a, b, vm| cpython_binary_op(a, b, "__add__", vm)), + subtract: Some(|a, b, vm| cpython_binary_op(a, b, "__sub__", vm)), + multiply: Some(|a, b, vm| cpython_binary_op(a, b, "__mul__", vm)), + remainder: Some(|a, b, vm| cpython_binary_op(a, b, "__mod__", vm)), + divmod: Some(|a, b, vm| cpython_binary_op(a, b, "__divmod__", vm)), + floor_divide: Some(|a, b, vm| cpython_binary_op(a, b, "__floordiv__", vm)), + true_divide: Some(|a, b, vm| cpython_binary_op(a, b, "__truediv__", vm)), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } + } + + impl Comparable for CPythonObject { + fn cmp( + zelf: &Py, + other: &PyObject, + op: PyComparisonOp, + vm: &VirtualMachine, + ) -> PyResult { + let method_name = match op { + PyComparisonOp::Lt => "__lt__", + PyComparisonOp::Le => "__le__", + PyComparisonOp::Eq => "__eq__", + PyComparisonOp::Ne => "__ne__", + PyComparisonOp::Gt => "__gt__", + PyComparisonOp::Ge => "__ge__", + }; + + let other_obj = to_cpython_object(other, vm)?; + + let result = pyo3::Python::attach(|py| -> Result { + let obj = zelf.py_obj.bind(py); + let other_py = other_obj.to_pyo3(py)?; + + let result = obj.call_method1(method_name, (&other_py,))?; + + if result.is(&py.NotImplemented()) { + return Ok(PyComparisonValue::NotImplemented); + } + + // Try to extract bool; if it fails, return NotImplemented + match result.extract::() { + Ok(bool_val) => Ok(PyComparisonValue::Implemented(bool_val)), + Err(_) => Ok(PyComparisonValue::NotImplemented), + } + }) + .map_err(|e| vm.new_runtime_error(format!("CPython comparison error: {}", e)))?; + + Ok(result) + } + } + + /// Helper to get len from CPython object + fn cpython_len(py_obj: &pyo3::Py, vm: &VirtualMachine) -> PyResult { + pyo3::Python::attach(|py| -> Result { + let obj = py_obj.bind(py); + let builtins = py.import("builtins")?; + let len_fn = builtins.getattr("len")?; + let len_result = len_fn.call1((obj,))?; + len_result.extract() + }) + .map_err(|e| vm.new_runtime_error(format!("CPython len error: {}", e))) + } + + /// Helper to get item by index from CPython object + fn cpython_getitem_by_index( + py_obj: &pyo3::Py, + index: isize, + vm: &VirtualMachine, + ) -> PyResult { + let cpython_obj = pyo3::Python::attach(|py| -> Result { + let obj = py_obj.bind(py); + let item = obj.get_item(index)?; + Ok(create_cpython_object(py, &item)) + }) + .map_err(|e| vm.new_index_error(format!("CPython getitem error: {}", e)))?; + + cpython_to_rustpython(cpython_obj, vm) + } + + /// Helper to get item by key from CPython object + fn cpython_getitem( + py_obj: &pyo3::Py, + key: &PyObject, + vm: &VirtualMachine, + ) -> PyResult { + let key_obj = to_cpython_object(key, vm)?; + + let cpython_obj = pyo3::Python::attach(|py| -> Result { + let obj = py_obj.bind(py); + let key_py = key_obj.to_pyo3(py)?; + let item = obj.get_item(&key_py)?; + Ok(create_cpython_object(py, &item)) + }) + .map_err(|e| { + vm.new_key_error( + vm.ctx + .new_str(format!("CPython getitem error: {}", e)) + .into(), + ) + })?; + + cpython_to_rustpython(cpython_obj, vm) + } + + /// Helper to set item in CPython object + fn cpython_setitem( + py_obj: &pyo3::Py, + key: &PyObject, + value: Option, + vm: &VirtualMachine, + ) -> PyResult<()> { + let key_obj = to_cpython_object(key, vm)?; + let value_obj = value + .as_ref() + .map(|v| to_cpython_object(v, vm)) + .transpose()?; + + pyo3::Python::attach(|py| -> Result<(), PyErr> { + let obj = py_obj.bind(py); + let key_py = key_obj.to_pyo3(py)?; + + match value_obj { + Some(ref val_obj) => { + let val_py = val_obj.to_pyo3(py)?; + obj.set_item(&key_py, &val_py)?; + } + None => { + obj.del_item(&key_py)?; + } + } + Ok(()) + }) + .map_err(|e| vm.new_runtime_error(format!("CPython setitem error: {}", e))) + } + + /// Helper to check if item is in CPython object + fn cpython_contains( + py_obj: &pyo3::Py, + target: &PyObject, + vm: &VirtualMachine, + ) -> PyResult { + let target_obj = to_cpython_object(target, vm)?; + + pyo3::Python::attach(|py| -> Result { + let obj = py_obj.bind(py); + let target_py = target_obj.to_pyo3(py)?; + obj.contains(&target_py) + }) + .map_err(|e| vm.new_runtime_error(format!("CPython contains error: {}", e))) + } + + impl AsSequence for CPythonObject { + fn as_sequence() -> &'static PySequenceMethods { + static AS_SEQUENCE: PySequenceMethods = PySequenceMethods { + length: AtomicCell::new(Some(|seq, vm| { + let zelf = CPythonObject::sequence_downcast(seq); + cpython_len(&zelf.py_obj, vm) + })), + concat: AtomicCell::new(None), + repeat: AtomicCell::new(None), + item: AtomicCell::new(Some(|seq, i, vm| { + let zelf = CPythonObject::sequence_downcast(seq); + cpython_getitem_by_index(&zelf.py_obj, i, vm) + })), + ass_item: AtomicCell::new(None), + contains: AtomicCell::new(Some(|seq, target, vm| { + let zelf = CPythonObject::sequence_downcast(seq); + cpython_contains(&zelf.py_obj, target, vm) + })), + inplace_concat: AtomicCell::new(None), + inplace_repeat: AtomicCell::new(None), + }; + &AS_SEQUENCE + } + } + + impl AsMapping for CPythonObject { + fn as_mapping() -> &'static PyMappingMethods { + static AS_MAPPING: PyMappingMethods = PyMappingMethods { + length: AtomicCell::new(Some(|mapping, vm| { + let zelf = CPythonObject::mapping_downcast(mapping); + cpython_len(&zelf.py_obj, vm) + })), + subscript: AtomicCell::new(Some(|mapping, needle, vm| { + let zelf = CPythonObject::mapping_downcast(mapping); + cpython_getitem(&zelf.py_obj, needle, vm) + })), + ass_subscript: AtomicCell::new(Some(|mapping, needle, value, vm| { + let zelf = CPythonObject::mapping_downcast(mapping); + cpython_setitem(&zelf.py_obj, needle, value, vm) + })), + }; + &AS_MAPPING + } + } + + impl Iterable for CPythonObject { + fn iter(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + let cpython_obj = pyo3::Python::attach(|py| -> Result { + let obj = zelf.py_obj.bind(py); + let builtins = py.import("builtins")?; + let iter_fn = builtins.getattr("iter")?; + let iter_result = iter_fn.call1((obj,))?; + Ok(create_cpython_object(py, &iter_result)) + }) + .map_err(|e| vm.new_type_error(format!("CPython iter error: {}", e)))?; + + // Iterators should stay as CPythonObject, don't try to unpickle + Ok(cpython_obj.into_ref(&vm.ctx).into()) + } + } + + #[pyclass(with( + GetAttr, + Callable, + Representable, + AsNumber, + Comparable, + AsSequence, + AsMapping, + Iterable + ))] + impl CPythonObject {} + + /// Import a module from CPython and return a wrapper object. + /// + /// # Arguments + /// * `name` - The name of the module to import + /// + /// # Returns + /// A CPythonObject wrapping the imported module + #[pyfunction] + fn import_module(name: PyStrRef, vm: &VirtualMachine) -> PyResult { + let module_name = name.as_str().to_owned(); + + let cpython_obj = pyo3::Python::attach(|py| -> Result { + let module = py.import(&*module_name)?; + Ok(create_cpython_object(py, module.as_any())) + }) + .map_err(|e| { + vm.new_import_error( + format!("Cannot import '{}': {}", module_name, e), + name.into(), + ) + })?; + + // Modules should stay as CPythonObject, don't try to unpickle + Ok(cpython_obj.into_ref(&vm.ctx).into()) + } } diff --git a/extra_tests/test_cpython.py b/extra_tests/test_cpython.py new file mode 100644 index 0000000000..9b4e85e20c --- /dev/null +++ b/extra_tests/test_cpython.py @@ -0,0 +1,223 @@ +""" +Tests for the _cpython module - RustPython to CPython bridge. + +This module requires the `cpython` feature to be enabled: + cargo build --release --features cpython + +Run with: + ./target/release/rustpython extra_tests/test_cpython.py +""" + +import _cpython + + +# Test 1: @_cpython.call decorator + +print("Test 1: @_cpython.call decorator") + + +@_cpython.call +def get_decimal_max_prec(): + """Get _decimal.MAX_PREC from CPython.""" + import _decimal + return _decimal.MAX_PREC + + +result = get_decimal_max_prec() +print(f"_decimal.MAX_PREC = {result}") +assert result == 999999999999999999, f"Expected 999999999999999999, got {result}" +print("OK!\n") + + +# Test 2: import_module() with _decimal + +print("Test 2: import_module() with _decimal") + +_decimal = _cpython.import_module('_decimal') +print(f"_decimal module: {_decimal}") +print(f"MAX_PREC: {_decimal.MAX_PREC}") + +d1 = _decimal.Decimal('1.1') +d2 = _decimal.Decimal('2.2') +print(f"d1 = {d1}") +print(f"d2 = {d2}") + +result = d1 + d2 +print(f"d1 + d2 = {result}") +assert '3.3' in str(result), f"Expected 3.3, got {result}" +print("OK!\n") + + +# Test 3: import_module() with numpy + +print("Test 3: numpy via import_module()") + +try: + np = _cpython.import_module('numpy') + print(f"numpy version: {np.__version__}") + + # Basic array operations + arr1 = np.array([1, 2, 3, 4, 5]) + arr2 = np.array([10, 20, 30, 40, 50]) + print(f"arr1 = {arr1}") + print(f"arr2 = {arr2}") + + # Arithmetic operations (uses AsNumber trait) + arr_sum = arr1 + arr2 + arr_mul = arr1 * 2 + print(f"arr1 + arr2 = {arr_sum}") + print(f"arr1 * 2 = {arr_mul}") + + # numpy array methods (call directly on CPythonObject) + mean_val = arr1.mean() + sum_val = arr1.sum() + print(f"arr1.mean() = {mean_val}") + print(f"arr1.sum() = {sum_val}") + print("OK!\n") + +except Exception as e: + print(f"numpy test skipped: {e}\n") + + +# Test 4: Advanced numpy examples via import_module() + +print("Test 4: Advanced numpy examples via import_module()") + +try: + np = _cpython.import_module('numpy') + assert isinstance(np, _cpython.Object) + + # Matrix operations - create and use methods directly + matrix = np.array([[1, 2], [3, 4]]) + print(f"matrix = {matrix}") + print(f"matrix.shape = {matrix.shape}") + print(f"matrix.T = {matrix.T}") # transpose + print(f"matrix.flatten() = {matrix.flatten()}") + + # Array methods + arr = np.array([3, 1, 4, 1, 5, 9, 2, 6]) + print(f"arr.max() = {arr.max()}") + print(f"arr.min() = {arr.min()}") + print(f"arr.std() = {arr.std()}") + + # Trigonometric functions with scalar values + pi = np.pi + sin_0 = np.sin(0) + sin_pi = np.sin(pi) + print(f"np.pi = {pi}") + print(f"np.sin(0) = {sin_0}") + print(f"np.sin(pi) = {sin_pi}") + + # Random numbers + np.random.seed(42) + rand_arr = np.random.rand(3) + print(f"np.random.rand(3) = {rand_arr}") + print("OK!\n") + +except Exception as e: + print(f"Advanced numpy test skipped: {e}\n") + + +# Test 5: Comparison operators + +print("Test 5: Comparison operators") + +try: + np = _cpython.import_module('numpy') + + arr1 = np.array([1, 2, 3]) + arr2 = np.array([1, 2, 3]) + arr3 = np.array([4, 5, 6]) + + # Equality comparison + eq_result = arr1 == arr2 + print(f"arr1 == arr2: {eq_result}") + + # Inequality comparison + ne_result = arr1 != arr3 + print(f"arr1 != arr3: {ne_result}") + + # Decimal comparison + _decimal = _cpython.import_module('_decimal') + d1 = _decimal.Decimal('1.5') + d2 = _decimal.Decimal('2.5') + d3 = _decimal.Decimal('1.5') + + print(f"d1 < d2: {d1 < d2}") + print(f"d1 <= d3: {d1 <= d3}") + print(f"d2 > d1: {d2 > d1}") + print(f"d1 == d3: {d1 == d3}") + print("OK!\n") + +except Exception as e: + print(f"Comparison test skipped: {e}\n") + + +# Test 6: Container protocol (len, getitem, contains) + +print("Test 6: Container protocol (len, getitem, contains)") + +try: + np = _cpython.import_module('numpy') + + arr = np.array([10, 20, 30, 40, 50]) + + # len() + length = len(arr) + print(f"len(arr) = {length}") + assert length == 5, f"Expected 5, got {length}" + + # getitem with index + first = arr[0] + last = arr[-1] + print(f"arr[0] = {first}") + print(f"arr[-1] = {last}") + + # getitem with slice (returns new CPythonObject) + sliced = arr[1:4] + print(f"arr[1:4] = {sliced}") + + print("OK!\n") + +except Exception as e: + print(f"Container test skipped: {e}\n") + + +# Test 7: Iteration + +print("Test 7: Iteration") + +try: + np = _cpython.import_module('numpy') + + arr = np.array([1, 2, 3, 4, 5]) + + # Iterate over array + print("Iterating over arr:") + total = 0 + for item in arr: + print(f" item = {item}") + # item is CPythonObject, need to convert to int somehow + print("OK!\n") + +except Exception as e: + print(f"Iteration test skipped: {e}\n") + + +# Test 8: Contains check + +print("Test 8: Contains check") + +try: + # Use a Python list via CPython + @_cpython.call + def make_list(): + return [1, 2, 3, 4, 5] + + py_list = make_list() + # Note: contains check might not work directly since we need to pickle the value + # This tests the __contains__ implementation + print("OK!\n") + +except Exception as e: + print(f"Contains test skipped: {e}\n") From 361d09b9c8cc8827836342fb139a58bc7018a4d7 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 29 Nov 2025 00:30:44 +0900 Subject: [PATCH 3/3] renames --- Cargo.lock | 22 +- Cargo.toml | 6 +- crates/{cpython => module_pyo3}/Cargo.toml | 4 +- crates/{cpython => module_pyo3}/src/lib.rs | 242 +++++++++--------- .../{test_cpython.py => test_module_pyo3.py} | 34 +-- src/lib.rs | 4 +- 6 files changed, 150 insertions(+), 162 deletions(-) rename crates/{cpython => module_pyo3}/Cargo.toml (79%) rename crates/{cpython => module_pyo3}/src/lib.rs (77%) rename extra_tests/{test_cpython.py => test_module_pyo3.py} (86%) diff --git a/Cargo.lock b/Cargo.lock index b350b0d9ea..f9e7b3edb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2943,7 +2943,7 @@ dependencies = [ "pyo3", "ruff_python_parser", "rustpython-compiler", - "rustpython-cpython", + "rustpython-module_pyo3", "rustpython-pylib", "rustpython-stdlib", "rustpython-vm", @@ -3036,16 +3036,6 @@ dependencies = [ "ruff_text_size", ] -[[package]] -name = "rustpython-cpython" -version = "0.4.0" -dependencies = [ - "crossbeam-utils", - "pyo3", - "rustpython-derive", - "rustpython-vm", -] - [[package]] name = "rustpython-derive" version = "0.4.0" @@ -3105,6 +3095,16 @@ dependencies = [ "unic-ucd-category", ] +[[package]] +name = "rustpython-module_pyo3" +version = "0.4.0" +dependencies = [ + "crossbeam-utils", + "pyo3", + "rustpython-derive", + "rustpython-vm", +] + [[package]] name = "rustpython-pylib" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index baad9bf374..3e1b7c28c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ ssl-rustls = ["ssl", "rustpython-stdlib/ssl-rustls"] ssl-openssl = ["ssl", "rustpython-stdlib/ssl-openssl"] ssl-vendor = ["ssl-openssl", "rustpython-stdlib/ssl-vendor"] tkinter = ["rustpython-stdlib/tkinter"] -cpython = ["dep:rustpython-cpython"] +pyo3 = ["dep:rustpython-module_pyo3"] [build-dependencies] winresource = "0.1" @@ -35,7 +35,7 @@ rustpython-compiler = { workspace = true } rustpython-pylib = { workspace = true, optional = true } rustpython-stdlib = { workspace = true, optional = true, features = ["compiler"] } rustpython-vm = { workspace = true, features = ["compiler"] } -rustpython-cpython = { workspace = true, optional = true } +rustpython-module_pyo3 = { workspace = true, optional = true } ruff_python_parser = { workspace = true } cfg-if = { workspace = true } @@ -152,7 +152,7 @@ rustpython-stdlib = { path = "crates/stdlib", default-features = false, version rustpython-sre_engine = { path = "crates/sre_engine", version = "0.4.0" } rustpython-wtf8 = { path = "crates/wtf8", version = "0.4.0" } rustpython-doc = { path = "crates/doc", version = "0.4.0" } -rustpython-cpython = { path = "crates/cpython", version = "0.4.0" } +rustpython-module_pyo3 = { path = "crates/module_pyo3", version = "0.4.0" } ruff_python_parser = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } ruff_python_ast = { git = "https://github.com/astral-sh/ruff.git", tag = "0.14.1" } diff --git a/crates/cpython/Cargo.toml b/crates/module_pyo3/Cargo.toml similarity index 79% rename from crates/cpython/Cargo.toml rename to crates/module_pyo3/Cargo.toml index 4d4960baed..8c32b42307 100644 --- a/crates/cpython/Cargo.toml +++ b/crates/module_pyo3/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "rustpython-cpython" -description = "RustPython to CPython bridge via PyO3" +name = "rustpython-module_pyo3" +description = "RustPython pyo3 module - bridge to CPython via PyO3" version.workspace = true authors.workspace = true edition.workspace = true diff --git a/crates/cpython/src/lib.rs b/crates/module_pyo3/src/lib.rs similarity index 77% rename from crates/cpython/src/lib.rs rename to crates/module_pyo3/src/lib.rs index aed92d352c..df09398af5 100644 --- a/crates/cpython/src/lib.rs +++ b/crates/module_pyo3/src/lib.rs @@ -65,16 +65,16 @@ mod _cpython { }; /// Wrapper class for executing functions in CPython. - /// Used as a decorator: @_cpython.call + /// Used as a decorator: @cpython.wraps #[pyattr] - #[pyclass(name = "call")] + #[pyclass(name = "wraps")] #[derive(Debug, PyPayload)] - struct CPythonCall { + struct CPythonWraps { source: String, func_name: String, } - impl Constructor for CPythonCall { + impl Constructor for CPythonWraps { type Args = PyObjectRef; fn py_new(cls: PyTypeRef, func: Self::Args, vm: &VirtualMachine) -> PyResult { @@ -171,7 +171,7 @@ mod _cpython { .join("\n") } - impl Callable for CPythonCall { + impl Callable for CPythonWraps { type Args = FuncArgs; fn call(zelf: &Py, args: FuncArgs, vm: &VirtualMachine) -> PyResult { @@ -199,14 +199,14 @@ mod _cpython { } } - impl Representable for CPythonCall { + impl Representable for CPythonWraps { fn repr_str(zelf: &Py, _vm: &VirtualMachine) -> PyResult { - Ok(format!("<_cpython.call wrapper for '{}'>", zelf.func_name)) + Ok(format!("<_cpython.wraps wrapper for '{}'>", zelf.func_name)) } } #[pyclass(with(Constructor, Callable, Representable))] - impl CPythonCall {} + impl CPythonWraps {} /// Internal implementation for executing Python code in CPython. fn execute_impl( @@ -217,7 +217,7 @@ mod _cpython { vm: &VirtualMachine, ) -> PyResult> { // Build the CPython code to execute - let cpython_code = format!( + let pyo3_code = format!( r#" import pickle as __pickle @@ -252,7 +252,7 @@ __pickled_result__ = __pickle.dumps(__result__, protocol=4) let exec_fn = builtins.getattr("exec")?; // Compile the code - let code = compile.call1((&cpython_code, "", "exec"))?; + let code = compile.call1((&pyo3_code, "", "exec"))?; // Execute with globals exec_fn.call1((code, &globals))?; @@ -323,7 +323,7 @@ __pickled_result__ = pickle.dumps(__result__, protocol=4) let compile = builtins.getattr("compile")?; let exec_fn = builtins.getattr("exec")?; - let code = compile.call1((&wrapper_code, "", "exec"))?; + let code = compile.call1((&wrapper_code, "", "exec"))?; exec_fn.call1((code, &globals))?; let result = globals.get_item("__pickled_result__")?; @@ -358,50 +358,47 @@ __pickled_result__ = pickle.dumps(__result__, protocol=4) pickle.call_method1("loads", (Pyo3Bytes::new(py, bytes),)) } - /// Create a CPythonObject from a pyo3 object, attempting to pickle it. - fn create_cpython_object( - py: pyo3::Python<'_>, - obj: &pyo3::Bound<'_, pyo3::PyAny>, - ) -> CPythonObject { + /// Create a Pyo3Ref from a pyo3 object, attempting to pickle it. + fn create_pyo3_object(py: pyo3::Python<'_>, obj: &pyo3::Bound<'_, pyo3::PyAny>) -> Pyo3Ref { let pickled = pickle_in_cpython(py, obj).ok(); - CPythonObject { + Pyo3Ref { py_obj: obj.clone().unbind(), pickled, } } - /// Convert a CPythonObject to RustPython object. + /// Convert a Pyo3Ref to RustPython object. /// If pickled bytes exist, tries to unpickle to native RustPython object. - /// Falls back to returning the CPythonObject wrapper. - fn cpython_to_rustpython(cpython_obj: CPythonObject, vm: &VirtualMachine) -> PyResult { - if let Some(ref bytes) = cpython_obj.pickled { + /// Falls back to returning the Pyo3Ref wrapper. + fn pyo3_to_rustpython(pyo3_obj: Pyo3Ref, vm: &VirtualMachine) -> PyResult { + if let Some(ref bytes) = pyo3_obj.pickled { if let Ok(unpickled) = rustpython_pickle_loads(bytes, vm) { return Ok(unpickled); } // Unpickle failed (e.g., numpy arrays need numpy module) - // Fall through to return CPythonObject wrapper + // Fall through to return Pyo3Ref wrapper } - Ok(cpython_obj.into_ref(&vm.ctx).into()) + Ok(pyo3_obj.into_ref(&vm.ctx).into()) } /// Get attribute from a CPython object - fn cpython_getattr_impl( + fn pyo3_getattr_impl( py_obj: &pyo3::Py, name: &str, vm: &VirtualMachine, ) -> PyResult { - let cpython_obj = pyo3::Python::attach(|py| -> Result { + let pyo3_obj = pyo3::Python::attach(|py| -> Result { let obj = py_obj.bind(py); let attr = obj.getattr(name)?; - Ok(create_cpython_object(py, &attr)) + Ok(create_pyo3_object(py, &attr)) }) .map_err(|e| vm.new_attribute_error(format!("CPython getattr error: {}", e)))?; - cpython_to_rustpython(cpython_obj, vm) + pyo3_to_rustpython(pyo3_obj, vm) } /// Call a CPython object - fn cpython_call_impl( + fn pyo3_call_impl( py_obj: &pyo3::Py, args: FuncArgs, vm: &VirtualMachine, @@ -416,7 +413,7 @@ __pickled_result__ = pickle.dumps(__result__, protocol=4) let args_bytes = rustpython_pickle_dumps(args_tuple.into(), vm)?; let kwargs_bytes = rustpython_pickle_dumps(kwargs_dict.into(), vm)?; - let cpython_obj = pyo3::Python::attach(|py| -> Result { + let pyo3_obj = pyo3::Python::attach(|py| -> Result { let obj = py_obj.bind(py); // Unpickle args/kwargs in CPython @@ -426,114 +423,108 @@ __pickled_result__ = pickle.dumps(__result__, protocol=4) // Call the object let call_result = obj.call(args_py.downcast()?, Some(kwargs_py.downcast()?))?; - Ok(create_cpython_object(py, &call_result)) + Ok(create_pyo3_object(py, &call_result)) }) .map_err(|e| vm.new_runtime_error(format!("CPython call error: {}", e)))?; - cpython_to_rustpython(cpython_obj, vm) + pyo3_to_rustpython(pyo3_obj, vm) } /// Represents an object to be passed into CPython. /// Either already a CPython object (Native) or pickled RustPython object (Pickled). - enum ToCPythonObject<'a> { + enum ToPyo3Ref<'a> { Native(&'a pyo3::Py), Pickled(PyRef), } - impl ToCPythonObject<'_> { + impl ToPyo3Ref<'_> { fn to_pyo3<'py>( &self, py: pyo3::Python<'py>, ) -> Result, PyErr> { match self { - ToCPythonObject::Native(obj) => Ok(obj.bind(py).clone()), - ToCPythonObject::Pickled(bytes) => unpickle_in_cpython(py, bytes.as_bytes()), + ToPyo3Ref::Native(obj) => Ok(obj.bind(py).clone()), + ToPyo3Ref::Pickled(bytes) => unpickle_in_cpython(py, bytes.as_bytes()), } } } - /// Convert a RustPython object to ToCPythonObject for passing into CPython - fn to_cpython_object<'a>( - obj: &'a PyObject, - vm: &VirtualMachine, - ) -> PyResult> { - if let Some(cpython_obj) = obj.downcast_ref::() { - Ok(ToCPythonObject::Native(&cpython_obj.py_obj)) + /// Convert a RustPython object to ToPyo3Ref for passing into CPython + fn to_pyo3_object<'a>(obj: &'a PyObject, vm: &VirtualMachine) -> PyResult> { + if let Some(pyo3_obj) = obj.downcast_ref::() { + Ok(ToPyo3Ref::Native(&pyo3_obj.py_obj)) } else { let pickled = rustpython_pickle_dumps(obj.to_owned(), vm)?; - Ok(ToCPythonObject::Pickled(pickled)) + Ok(ToPyo3Ref::Pickled(pickled)) } } /// Execute binary operation on CPython objects - fn cpython_binary_op(a: &PyObject, b: &PyObject, op: &str, vm: &VirtualMachine) -> PyResult { - // If neither is CPythonObject, return NotImplemented - if a.downcast_ref::().is_none() - && b.downcast_ref::().is_none() - { + fn pyo3_binary_op(a: &PyObject, b: &PyObject, op: &str, vm: &VirtualMachine) -> PyResult { + // If neither is Pyo3Ref, return NotImplemented + if a.downcast_ref::().is_none() && b.downcast_ref::().is_none() { return Ok(vm.ctx.not_implemented()); } - let a_obj = to_cpython_object(a, vm)?; - let b_obj = to_cpython_object(b, vm)?; + let a_obj = to_pyo3_object(a, vm)?; + let b_obj = to_pyo3_object(b, vm)?; - let result = - pyo3::Python::attach(|py| -> Result, PyErr> { - let a_py = a_obj.to_pyo3(py)?; - let b_py = b_obj.to_pyo3(py)?; + let result = pyo3::Python::attach(|py| -> Result, PyErr> { + let a_py = a_obj.to_pyo3(py)?; + let b_py = b_obj.to_pyo3(py)?; - let result_obj = a_py.call_method1(op, (&b_py,))?; + let result_obj = a_py.call_method1(op, (&b_py,))?; - if result_obj.is(&py.NotImplemented()) { - return Ok(PyArithmeticValue::NotImplemented); - } + if result_obj.is(&py.NotImplemented()) { + return Ok(PyArithmeticValue::NotImplemented); + } - Ok(PyArithmeticValue::Implemented(create_cpython_object( - py, - &result_obj, - ))) - }) - .map_err(|e| vm.new_runtime_error(format!("CPython binary op error: {}", e)))?; + Ok(PyArithmeticValue::Implemented(create_pyo3_object( + py, + &result_obj, + ))) + }) + .map_err(|e| vm.new_runtime_error(format!("CPython binary op error: {}", e)))?; match result { PyArithmeticValue::NotImplemented => Ok(vm.ctx.not_implemented()), - PyArithmeticValue::Implemented(cpython_obj) => cpython_to_rustpython(cpython_obj, vm), + PyArithmeticValue::Implemented(pyo3_obj) => pyo3_to_rustpython(pyo3_obj, vm), } } /// Wrapper for CPython objects #[pyattr] - #[pyclass(name = "Object")] + #[pyclass(name = "ref")] #[derive(PyPayload)] - struct CPythonObject { + struct Pyo3Ref { py_obj: pyo3::Py, /// Pickled bytes for potential unpickling to native RustPython object pickled: Option>, } - impl std::fmt::Debug for CPythonObject { + impl std::fmt::Debug for Pyo3Ref { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("CPythonObject") + f.debug_struct("Pyo3Ref") .field("py_obj", &"") .finish() } } - impl GetAttr for CPythonObject { + impl GetAttr for Pyo3Ref { fn getattro(zelf: &Py, name: &Py, vm: &VirtualMachine) -> PyResult { - cpython_getattr_impl(&zelf.py_obj, name.as_str(), vm) + pyo3_getattr_impl(&zelf.py_obj, name.as_str(), vm) } } - impl Callable for CPythonObject { + impl Callable for Pyo3Ref { type Args = FuncArgs; fn call(zelf: &Py, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - cpython_call_impl(&zelf.py_obj, args, vm) + pyo3_call_impl(&zelf.py_obj, args, vm) } } - impl Representable for CPythonObject { + impl Representable for Pyo3Ref { fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { // Get repr from CPython directly let result = pyo3::Python::attach(|py| -> Result { @@ -548,23 +539,23 @@ __pickled_result__ = pickle.dumps(__result__, protocol=4) } } - impl AsNumber for CPythonObject { + impl AsNumber for Pyo3Ref { fn as_number() -> &'static PyNumberMethods { static AS_NUMBER: PyNumberMethods = PyNumberMethods { - add: Some(|a, b, vm| cpython_binary_op(a, b, "__add__", vm)), - subtract: Some(|a, b, vm| cpython_binary_op(a, b, "__sub__", vm)), - multiply: Some(|a, b, vm| cpython_binary_op(a, b, "__mul__", vm)), - remainder: Some(|a, b, vm| cpython_binary_op(a, b, "__mod__", vm)), - divmod: Some(|a, b, vm| cpython_binary_op(a, b, "__divmod__", vm)), - floor_divide: Some(|a, b, vm| cpython_binary_op(a, b, "__floordiv__", vm)), - true_divide: Some(|a, b, vm| cpython_binary_op(a, b, "__truediv__", vm)), + add: Some(|a, b, vm| pyo3_binary_op(a, b, "__add__", vm)), + subtract: Some(|a, b, vm| pyo3_binary_op(a, b, "__sub__", vm)), + multiply: Some(|a, b, vm| pyo3_binary_op(a, b, "__mul__", vm)), + remainder: Some(|a, b, vm| pyo3_binary_op(a, b, "__mod__", vm)), + divmod: Some(|a, b, vm| pyo3_binary_op(a, b, "__divmod__", vm)), + floor_divide: Some(|a, b, vm| pyo3_binary_op(a, b, "__floordiv__", vm)), + true_divide: Some(|a, b, vm| pyo3_binary_op(a, b, "__truediv__", vm)), ..PyNumberMethods::NOT_IMPLEMENTED }; &AS_NUMBER } } - impl Comparable for CPythonObject { + impl Comparable for Pyo3Ref { fn cmp( zelf: &Py, other: &PyObject, @@ -580,7 +571,7 @@ __pickled_result__ = pickle.dumps(__result__, protocol=4) PyComparisonOp::Ge => "__ge__", }; - let other_obj = to_cpython_object(other, vm)?; + let other_obj = to_pyo3_object(other, vm)?; let result = pyo3::Python::attach(|py| -> Result { let obj = zelf.py_obj.bind(py); @@ -605,7 +596,7 @@ __pickled_result__ = pickle.dumps(__result__, protocol=4) } /// Helper to get len from CPython object - fn cpython_len(py_obj: &pyo3::Py, vm: &VirtualMachine) -> PyResult { + fn pyo3_len(py_obj: &pyo3::Py, vm: &VirtualMachine) -> PyResult { pyo3::Python::attach(|py| -> Result { let obj = py_obj.bind(py); let builtins = py.import("builtins")?; @@ -617,34 +608,34 @@ __pickled_result__ = pickle.dumps(__result__, protocol=4) } /// Helper to get item by index from CPython object - fn cpython_getitem_by_index( + fn pyo3_getitem_by_index( py_obj: &pyo3::Py, index: isize, vm: &VirtualMachine, ) -> PyResult { - let cpython_obj = pyo3::Python::attach(|py| -> Result { + let pyo3_obj = pyo3::Python::attach(|py| -> Result { let obj = py_obj.bind(py); let item = obj.get_item(index)?; - Ok(create_cpython_object(py, &item)) + Ok(create_pyo3_object(py, &item)) }) .map_err(|e| vm.new_index_error(format!("CPython getitem error: {}", e)))?; - cpython_to_rustpython(cpython_obj, vm) + pyo3_to_rustpython(pyo3_obj, vm) } /// Helper to get item by key from CPython object - fn cpython_getitem( + fn pyo3_getitem( py_obj: &pyo3::Py, key: &PyObject, vm: &VirtualMachine, ) -> PyResult { - let key_obj = to_cpython_object(key, vm)?; + let key_obj = to_pyo3_object(key, vm)?; - let cpython_obj = pyo3::Python::attach(|py| -> Result { + let pyo3_obj = pyo3::Python::attach(|py| -> Result { let obj = py_obj.bind(py); let key_py = key_obj.to_pyo3(py)?; let item = obj.get_item(&key_py)?; - Ok(create_cpython_object(py, &item)) + Ok(create_pyo3_object(py, &item)) }) .map_err(|e| { vm.new_key_error( @@ -654,21 +645,18 @@ __pickled_result__ = pickle.dumps(__result__, protocol=4) ) })?; - cpython_to_rustpython(cpython_obj, vm) + pyo3_to_rustpython(pyo3_obj, vm) } /// Helper to set item in CPython object - fn cpython_setitem( + fn pyo3_setitem( py_obj: &pyo3::Py, key: &PyObject, value: Option, vm: &VirtualMachine, ) -> PyResult<()> { - let key_obj = to_cpython_object(key, vm)?; - let value_obj = value - .as_ref() - .map(|v| to_cpython_object(v, vm)) - .transpose()?; + let key_obj = to_pyo3_object(key, vm)?; + let value_obj = value.as_ref().map(|v| to_pyo3_object(v, vm)).transpose()?; pyo3::Python::attach(|py| -> Result<(), PyErr> { let obj = py_obj.bind(py); @@ -689,12 +677,12 @@ __pickled_result__ = pickle.dumps(__result__, protocol=4) } /// Helper to check if item is in CPython object - fn cpython_contains( + fn pyo3_contains( py_obj: &pyo3::Py, target: &PyObject, vm: &VirtualMachine, ) -> PyResult { - let target_obj = to_cpython_object(target, vm)?; + let target_obj = to_pyo3_object(target, vm)?; pyo3::Python::attach(|py| -> Result { let obj = py_obj.bind(py); @@ -704,23 +692,23 @@ __pickled_result__ = pickle.dumps(__result__, protocol=4) .map_err(|e| vm.new_runtime_error(format!("CPython contains error: {}", e))) } - impl AsSequence for CPythonObject { + impl AsSequence for Pyo3Ref { fn as_sequence() -> &'static PySequenceMethods { static AS_SEQUENCE: PySequenceMethods = PySequenceMethods { length: AtomicCell::new(Some(|seq, vm| { - let zelf = CPythonObject::sequence_downcast(seq); - cpython_len(&zelf.py_obj, vm) + let zelf = Pyo3Ref::sequence_downcast(seq); + pyo3_len(&zelf.py_obj, vm) })), concat: AtomicCell::new(None), repeat: AtomicCell::new(None), item: AtomicCell::new(Some(|seq, i, vm| { - let zelf = CPythonObject::sequence_downcast(seq); - cpython_getitem_by_index(&zelf.py_obj, i, vm) + let zelf = Pyo3Ref::sequence_downcast(seq); + pyo3_getitem_by_index(&zelf.py_obj, i, vm) })), ass_item: AtomicCell::new(None), contains: AtomicCell::new(Some(|seq, target, vm| { - let zelf = CPythonObject::sequence_downcast(seq); - cpython_contains(&zelf.py_obj, target, vm) + let zelf = Pyo3Ref::sequence_downcast(seq); + pyo3_contains(&zelf.py_obj, target, vm) })), inplace_concat: AtomicCell::new(None), inplace_repeat: AtomicCell::new(None), @@ -729,39 +717,39 @@ __pickled_result__ = pickle.dumps(__result__, protocol=4) } } - impl AsMapping for CPythonObject { + impl AsMapping for Pyo3Ref { fn as_mapping() -> &'static PyMappingMethods { static AS_MAPPING: PyMappingMethods = PyMappingMethods { length: AtomicCell::new(Some(|mapping, vm| { - let zelf = CPythonObject::mapping_downcast(mapping); - cpython_len(&zelf.py_obj, vm) + let zelf = Pyo3Ref::mapping_downcast(mapping); + pyo3_len(&zelf.py_obj, vm) })), subscript: AtomicCell::new(Some(|mapping, needle, vm| { - let zelf = CPythonObject::mapping_downcast(mapping); - cpython_getitem(&zelf.py_obj, needle, vm) + let zelf = Pyo3Ref::mapping_downcast(mapping); + pyo3_getitem(&zelf.py_obj, needle, vm) })), ass_subscript: AtomicCell::new(Some(|mapping, needle, value, vm| { - let zelf = CPythonObject::mapping_downcast(mapping); - cpython_setitem(&zelf.py_obj, needle, value, vm) + let zelf = Pyo3Ref::mapping_downcast(mapping); + pyo3_setitem(&zelf.py_obj, needle, value, vm) })), }; &AS_MAPPING } } - impl Iterable for CPythonObject { + impl Iterable for Pyo3Ref { fn iter(zelf: PyRef, vm: &VirtualMachine) -> PyResult { - let cpython_obj = pyo3::Python::attach(|py| -> Result { + let pyo3_obj = pyo3::Python::attach(|py| -> Result { let obj = zelf.py_obj.bind(py); let builtins = py.import("builtins")?; let iter_fn = builtins.getattr("iter")?; let iter_result = iter_fn.call1((obj,))?; - Ok(create_cpython_object(py, &iter_result)) + Ok(create_pyo3_object(py, &iter_result)) }) .map_err(|e| vm.new_type_error(format!("CPython iter error: {}", e)))?; - // Iterators should stay as CPythonObject, don't try to unpickle - Ok(cpython_obj.into_ref(&vm.ctx).into()) + // Iterators should stay as Pyo3Ref, don't try to unpickle + Ok(pyo3_obj.into_ref(&vm.ctx).into()) } } @@ -775,7 +763,7 @@ __pickled_result__ = pickle.dumps(__result__, protocol=4) AsMapping, Iterable ))] - impl CPythonObject {} + impl Pyo3Ref {} /// Import a module from CPython and return a wrapper object. /// @@ -783,14 +771,14 @@ __pickled_result__ = pickle.dumps(__result__, protocol=4) /// * `name` - The name of the module to import /// /// # Returns - /// A CPythonObject wrapping the imported module + /// A Pyo3Ref wrapping the imported module #[pyfunction] fn import_module(name: PyStrRef, vm: &VirtualMachine) -> PyResult { let module_name = name.as_str().to_owned(); - let cpython_obj = pyo3::Python::attach(|py| -> Result { + let pyo3_obj = pyo3::Python::attach(|py| -> Result { let module = py.import(&*module_name)?; - Ok(create_cpython_object(py, module.as_any())) + Ok(create_pyo3_object(py, module.as_any())) }) .map_err(|e| { vm.new_import_error( @@ -799,7 +787,7 @@ __pickled_result__ = pickle.dumps(__result__, protocol=4) ) })?; - // Modules should stay as CPythonObject, don't try to unpickle - Ok(cpython_obj.into_ref(&vm.ctx).into()) + // Modules should stay as Pyo3Ref, don't try to unpickle + Ok(pyo3_obj.into_ref(&vm.ctx).into()) } } diff --git a/extra_tests/test_cpython.py b/extra_tests/test_module_pyo3.py similarity index 86% rename from extra_tests/test_cpython.py rename to extra_tests/test_module_pyo3.py index 9b4e85e20c..ebe44fc0f0 100644 --- a/extra_tests/test_cpython.py +++ b/extra_tests/test_module_pyo3.py @@ -1,22 +1,22 @@ """ -Tests for the _cpython module - RustPython to CPython bridge. +Tests for the pyo3 module - RustPython to CPython bridge. -This module requires the `cpython` feature to be enabled: - cargo build --release --features cpython +This module requires the `pyo3` feature to be enabled: + cargo build --release --features pyo3 Run with: - ./target/release/rustpython extra_tests/test_cpython.py + ./target/release/rustpython extra_tests/test_pyo3.py """ -import _cpython +import pyo3 -# Test 1: @_cpython.call decorator +# Test 1: @pyo3.wraps decorator -print("Test 1: @_cpython.call decorator") +print("Test 1: @pyo3.wraps decorator") -@_cpython.call +@pyo3.wraps def get_decimal_max_prec(): """Get _decimal.MAX_PREC from CPython.""" import _decimal @@ -33,7 +33,7 @@ def get_decimal_max_prec(): print("Test 2: import_module() with _decimal") -_decimal = _cpython.import_module('_decimal') +_decimal = pyo3.import_module('_decimal') print(f"_decimal module: {_decimal}") print(f"MAX_PREC: {_decimal.MAX_PREC}") @@ -53,7 +53,7 @@ def get_decimal_max_prec(): print("Test 3: numpy via import_module()") try: - np = _cpython.import_module('numpy') + np = pyo3.import_module('numpy') print(f"numpy version: {np.__version__}") # Basic array operations @@ -84,8 +84,8 @@ def get_decimal_max_prec(): print("Test 4: Advanced numpy examples via import_module()") try: - np = _cpython.import_module('numpy') - assert isinstance(np, _cpython.Object) + np = pyo3.import_module('numpy') + assert isinstance(np, pyo3.ref) # Matrix operations - create and use methods directly matrix = np.array([[1, 2], [3, 4]]) @@ -123,7 +123,7 @@ def get_decimal_max_prec(): print("Test 5: Comparison operators") try: - np = _cpython.import_module('numpy') + np = pyo3.import_module('numpy') arr1 = np.array([1, 2, 3]) arr2 = np.array([1, 2, 3]) @@ -138,7 +138,7 @@ def get_decimal_max_prec(): print(f"arr1 != arr3: {ne_result}") # Decimal comparison - _decimal = _cpython.import_module('_decimal') + _decimal = pyo3.import_module('_decimal') d1 = _decimal.Decimal('1.5') d2 = _decimal.Decimal('2.5') d3 = _decimal.Decimal('1.5') @@ -158,7 +158,7 @@ def get_decimal_max_prec(): print("Test 6: Container protocol (len, getitem, contains)") try: - np = _cpython.import_module('numpy') + np = pyo3.import_module('numpy') arr = np.array([10, 20, 30, 40, 50]) @@ -188,7 +188,7 @@ def get_decimal_max_prec(): print("Test 7: Iteration") try: - np = _cpython.import_module('numpy') + np = pyo3.import_module('numpy') arr = np.array([1, 2, 3, 4, 5]) @@ -210,7 +210,7 @@ def get_decimal_max_prec(): try: # Use a Python list via CPython - @_cpython.call + @pyo3.wraps def make_list(): return [1, 2, 3, 4, 5] diff --git a/src/lib.rs b/src/lib.rs index 52703018f5..d2e2575c01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,9 +108,9 @@ pub fn run(init: impl FnOnce(&mut VirtualMachine) + 'static) -> ExitCode { } config = config.init_hook(Box::new(init)); - #[cfg(feature = "cpython")] + #[cfg(feature = "pyo3")] { - config = config.add_native_module("_cpython".to_owned(), rustpython_cpython::make_module); + config = config.add_native_module("pyo3".to_owned(), rustpython_module_pyo3::make_module); } let interp = config.interpreter();