From c35986aa58202aa7a2bfe80a6be460107ba5423f Mon Sep 17 00:00:00 2001 From: Mohamed Gharbi Date: Sun, 28 Dec 2025 14:16:48 +0100 Subject: [PATCH 1/6] Refine documentation in interpreter.rs Improve InterpreterConfig with convenience methods (with_debug, add_path, add_paths), better documentation with working examples, refactored stdlib setup into focused functions, and comprehensive unit tests while maintaining 100% backward compatibility. --- src/interpreter.rs | 284 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 227 insertions(+), 57 deletions(-) diff --git a/src/interpreter.rs b/src/interpreter.rs index b4fd319cdae..6c8ae104942 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -2,39 +2,49 @@ use rustpython_vm::{Interpreter, PyRef, Settings, VirtualMachine, builtins::PyMo pub type InitHook = Box; -/// The convenient way to create [rustpython_vm::Interpreter] with stdlib and other stuffs. +/// The convenient way to create [rustpython_vm::Interpreter] with stdlib and other components. /// -/// Basic usage: -/// ``` -/// let interpreter = rustpython::InterpreterConfig::new() +/// # Basic Usage +/// ```no_run +/// use rustpython::InterpreterConfig; +/// +/// let interpreter = InterpreterConfig::new() /// .init_stdlib() /// .interpreter(); /// ``` /// -/// To override [rustpython_vm::Settings]: -/// ``` +/// # Override Settings +/// ```no_run /// use rustpython_vm::Settings; -/// // Override your settings here. +/// use rustpython::InterpreterConfig; +/// /// let mut settings = Settings::default(); /// settings.debug = 1; -/// // You may want to add paths to `rustpython_vm::Settings::path_list` to allow import python libraries. -/// settings.path_list.push("Lib".to_owned()); // add standard library directory -/// settings.path_list.push("".to_owned()); // add current working directory -/// let interpreter = rustpython::InterpreterConfig::new() +/// // Add paths to allow importing Python libraries +/// settings.path_list.push("Lib".to_owned()); // standard library directory +/// settings.path_list.push("".to_owned()); // current working directory +/// +/// let interpreter = InterpreterConfig::new() /// .settings(settings) /// .interpreter(); /// ``` /// -/// To add native modules: -/// ```compile_fail -/// let interpreter = rustpython::InterpreterConfig::new() +/// # Add Native Modules +/// ```no_run +/// use rustpython::InterpreterConfig; +/// use rustpython_vm::{VirtualMachine, PyRef, builtins::PyModule}; +/// +/// fn make_custom_module(vm: &VirtualMachine) -> PyRef { +/// // Your module implementation +/// # todo!() +/// } +/// +/// let interpreter = InterpreterConfig::new() /// .init_stdlib() -/// .init_hook(Box::new(|vm| { -/// vm.add_native_module( -/// "your_module_name".to_owned(), -/// Box::new(your_module::make_module), -/// ); -/// })) +/// .add_native_module( +/// "your_module_name".to_owned(), +/// make_custom_module, +/// ) /// .interpreter(); /// ``` #[derive(Default)] @@ -44,9 +54,15 @@ pub struct InterpreterConfig { } impl InterpreterConfig { + /// Create a new interpreter configuration with default settings pub fn new() -> Self { Self::default() } + + /// Build the interpreter with the current configuration + /// + /// # Panics + /// May panic if initialization hooks encounter fatal errors pub fn interpreter(self) -> Interpreter { let settings = self.settings.unwrap_or_default(); Interpreter::with_init(settings, |vm| { @@ -56,14 +72,37 @@ impl InterpreterConfig { }) } + /// Set custom settings for the interpreter + /// + /// If called multiple times, only the last settings will be used pub fn settings(mut self, settings: Settings) -> Self { self.settings = Some(settings); self } + + /// Add a custom initialization hook + /// + /// Hooks are executed in the order they are added during interpreter creation pub fn init_hook(mut self, hook: InitHook) -> Self { self.init_hooks.push(hook); self } + + /// Add a native module to the interpreter + /// + /// # Arguments + /// * `name` - The module name that will be used for imports + /// * `make_module` - Function that creates the module when called + /// + /// # Example + /// ```no_run + /// # use rustpython::InterpreterConfig; + /// # use rustpython_vm::{VirtualMachine, PyRef, builtins::PyModule}; + /// # fn my_module(vm: &VirtualMachine) -> PyRef { todo!() } + /// let interpreter = InterpreterConfig::new() + /// .add_native_module("mymodule".to_owned(), my_module) + /// .interpreter(); + /// ``` pub fn add_native_module( self, name: String, @@ -73,56 +112,187 @@ impl InterpreterConfig { vm.add_native_module(name, Box::new(make_module)) })) } + + /// Initialize the Python standard library + /// + /// This adds all standard library modules to the interpreter. + /// Requires the `stdlib` feature to be enabled at compile time. #[cfg(feature = "stdlib")] pub fn init_stdlib(self) -> Self { self.init_hook(Box::new(init_stdlib)) } + + /// Initialize the Python standard library (no-op without stdlib feature) + /// + /// When the `stdlib` feature is not enabled, this method does nothing + /// and prints a warning. Enable the `stdlib` feature to use the standard library. + #[cfg(not(feature = "stdlib"))] + pub fn init_stdlib(self) -> Self { + eprintln!("Warning: stdlib feature is not enabled. Standard library will not be available."); + self + } + + /// Convenience method to set the debug level + /// + /// # Example + /// ```no_run + /// # use rustpython::InterpreterConfig; + /// let interpreter = InterpreterConfig::new() + /// .with_debug(1) + /// .interpreter(); + /// ``` + pub fn with_debug(mut self, level: u8) -> Self { + self.settings.get_or_insert_with(Default::default).debug = level; + self + } + + /// Convenience method to add a single path to the module search paths + /// + /// # Example + /// ```no_run + /// # use rustpython::InterpreterConfig; + /// let interpreter = InterpreterConfig::new() + /// .add_path("Lib") + /// .add_path(".") + /// .interpreter(); + /// ``` + pub fn add_path(mut self, path: impl Into) -> Self { + self.settings.get_or_insert_with(Default::default).path_list.push(path.into()); + self + } + + /// Add multiple paths to the module search paths at once + /// + /// # Example + /// ```no_run + /// # use rustpython::InterpreterConfig; + /// let interpreter = InterpreterConfig::new() + /// .add_paths(vec!["Lib", ".", "custom_modules"]) + /// .interpreter(); + /// ``` + pub fn add_paths(mut self, paths: I) -> Self + where + I: IntoIterator, + S: Into, + { + let settings = self.settings.get_or_insert_with(Default::default); + settings.path_list.extend(paths.into_iter().map(Into::into)); + self + } } +/// Initialize the standard library modules +/// +/// This function sets up both native modules and handles frozen/dynamic stdlib loading #[cfg(feature = "stdlib")] pub fn init_stdlib(vm: &mut VirtualMachine) { vm.add_native_modules(rustpython_stdlib::get_module_inits()); - // if we're on freeze-stdlib, the core stdlib modules will be included anyway #[cfg(feature = "freeze-stdlib")] - { - vm.add_frozen(rustpython_pylib::FROZEN_STDLIB); + setup_frozen_stdlib(vm); + + #[cfg(not(feature = "freeze-stdlib"))] + setup_dynamic_stdlib(vm); +} - // FIXME: Remove this hack once sys._stdlib_dir is properly implemented or _frozen_importlib doesn't depend on it anymore. - assert!(vm.sys_module.get_attr("_stdlib_dir", vm).is_err()); - vm.sys_module - .set_attr( - "_stdlib_dir", - vm.new_pyobj(rustpython_pylib::LIB_PATH.to_owned()), - vm, - ) - .unwrap(); +/// Setup frozen standard library +/// +/// Used when the stdlib is compiled into the binary +#[cfg(all(feature = "stdlib", feature = "freeze-stdlib"))] +fn setup_frozen_stdlib(vm: &mut VirtualMachine) { + vm.add_frozen(rustpython_pylib::FROZEN_STDLIB); + + // FIXME: Remove this hack once sys._stdlib_dir is properly implemented + // or _frozen_importlib doesn't depend on it anymore. + // The assert ensures _stdlib_dir doesn't already exist before we set it + assert!(vm.sys_module.get_attr("_stdlib_dir", vm).is_err()); + vm.sys_module + .set_attr( + "_stdlib_dir", + vm.new_pyobj(rustpython_pylib::LIB_PATH.to_owned()), + vm, + ) + .unwrap(); +} + +/// Setup dynamic standard library loading from filesystem +/// +/// Used when the stdlib is loaded from disk at runtime +#[cfg(all(feature = "stdlib", not(feature = "freeze-stdlib")))] +fn setup_dynamic_stdlib(vm: &mut VirtualMachine) { + use rustpython_vm::common::rc::PyRc; + + let state = PyRc::get_mut(&mut vm.state).unwrap(); + + let additional_paths = collect_stdlib_paths(); + + // Insert at the beginning so stdlib comes before user paths + for path in additional_paths.into_iter().rev() { + state.config.paths.module_search_paths.insert(0, path); } +} - #[cfg(not(feature = "freeze-stdlib"))] - { - use rustpython_vm::common::rc::PyRc; - - let state = PyRc::get_mut(&mut vm.state).unwrap(); - - // Collect additional paths to add - let mut additional_paths = Vec::new(); - - // BUILDTIME_RUSTPYTHONPATH should be set when distributing - if let Some(paths) = option_env!("BUILDTIME_RUSTPYTHONPATH") { - additional_paths.extend( - crate::settings::split_paths(paths) - .map(|path| path.into_os_string().into_string().unwrap()), - ) - } else { - #[cfg(feature = "rustpython-pylib")] - additional_paths.push(rustpython_pylib::LIB_PATH.to_owned()) - } - - // Add to both path_list (for compatibility) and module_search_paths (for sys.path) - // Insert at the beginning so stdlib comes before user paths - for path in additional_paths.into_iter().rev() { - state.config.paths.module_search_paths.insert(0, path); - } +/// Collect standard library paths from build-time configuration +/// +/// Checks BUILDTIME_RUSTPYTHONPATH environment variable or uses default pylib path +#[cfg(all(feature = "stdlib", not(feature = "freeze-stdlib")))] +fn collect_stdlib_paths() -> Vec { + let mut additional_paths = Vec::new(); + + // BUILDTIME_RUSTPYTHONPATH should be set when distributing + if let Some(paths) = option_env!("BUILDTIME_RUSTPYTHONPATH") { + additional_paths.extend( + crate::settings::split_paths(paths) + .map(|path| path.into_os_string().into_string().unwrap()), + ) + } else { + #[cfg(feature = "rustpython-pylib")] + additional_paths.push(rustpython_pylib::LIB_PATH.to_owned()) + } + + additional_paths +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_default_config() { + let config = InterpreterConfig::new(); + assert!(config.settings.is_none()); + assert!(config.init_hooks.is_empty()); + } + + #[test] + fn test_with_debug() { + let config = InterpreterConfig::new().with_debug(2); + let settings = config.settings.unwrap(); + assert_eq!(settings.debug, 2); + } + + #[test] + fn test_add_single_path() { + let config = InterpreterConfig::new().add_path("test/path"); + let settings = config.settings.unwrap(); + assert_eq!(settings.path_list.len(), 1); + assert_eq!(settings.path_list[0], "test/path"); + } + + #[test] + fn test_add_multiple_paths_sequential() { + let config = InterpreterConfig::new() + .add_path("path1") + .add_path("path2"); + let settings = config.settings.unwrap(); + assert_eq!(settings.path_list.len(), 2); + } + + #[test] + fn test_add_paths_batch() { + let paths = vec!["path1", "path2", "path3"]; + let config = InterpreterConfig::new().add_paths(paths); + let settings = config.settings.unwrap(); + assert_eq!(settings.path_list.len(), 3); } } From 2cae714bc45d95c04b9a633f2dc4b4d2768eea3c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 28 Dec 2025 13:18:37 +0000 Subject: [PATCH 2/6] Auto-format: cargo fmt --all --- src/interpreter.rs | 55 ++++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/src/interpreter.rs b/src/interpreter.rs index 6c8ae104942..7fa2acef1f9 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -7,7 +7,7 @@ pub type InitHook = Box; /// # Basic Usage /// ```no_run /// use rustpython::InterpreterConfig; -/// +/// /// let interpreter = InterpreterConfig::new() /// .init_stdlib() /// .interpreter(); @@ -17,13 +17,13 @@ pub type InitHook = Box; /// ```no_run /// use rustpython_vm::Settings; /// use rustpython::InterpreterConfig; -/// +/// /// let mut settings = Settings::default(); /// settings.debug = 1; /// // Add paths to allow importing Python libraries /// settings.path_list.push("Lib".to_owned()); // standard library directory /// settings.path_list.push("".to_owned()); // current working directory -/// +/// /// let interpreter = InterpreterConfig::new() /// .settings(settings) /// .interpreter(); @@ -33,12 +33,12 @@ pub type InitHook = Box; /// ```no_run /// use rustpython::InterpreterConfig; /// use rustpython_vm::{VirtualMachine, PyRef, builtins::PyModule}; -/// +/// /// fn make_custom_module(vm: &VirtualMachine) -> PyRef { /// // Your module implementation /// # todo!() /// } -/// +/// /// let interpreter = InterpreterConfig::new() /// .init_stdlib() /// .add_native_module( @@ -60,7 +60,7 @@ impl InterpreterConfig { } /// Build the interpreter with the current configuration - /// + /// /// # Panics /// May panic if initialization hooks encounter fatal errors pub fn interpreter(self) -> Interpreter { @@ -73,7 +73,7 @@ impl InterpreterConfig { } /// Set custom settings for the interpreter - /// + /// /// If called multiple times, only the last settings will be used pub fn settings(mut self, settings: Settings) -> Self { self.settings = Some(settings); @@ -81,7 +81,7 @@ impl InterpreterConfig { } /// Add a custom initialization hook - /// + /// /// Hooks are executed in the order they are added during interpreter creation pub fn init_hook(mut self, hook: InitHook) -> Self { self.init_hooks.push(hook); @@ -89,11 +89,11 @@ impl InterpreterConfig { } /// Add a native module to the interpreter - /// + /// /// # Arguments /// * `name` - The module name that will be used for imports /// * `make_module` - Function that creates the module when called - /// + /// /// # Example /// ```no_run /// # use rustpython::InterpreterConfig; @@ -114,7 +114,7 @@ impl InterpreterConfig { } /// Initialize the Python standard library - /// + /// /// This adds all standard library modules to the interpreter. /// Requires the `stdlib` feature to be enabled at compile time. #[cfg(feature = "stdlib")] @@ -123,17 +123,19 @@ impl InterpreterConfig { } /// Initialize the Python standard library (no-op without stdlib feature) - /// + /// /// When the `stdlib` feature is not enabled, this method does nothing /// and prints a warning. Enable the `stdlib` feature to use the standard library. #[cfg(not(feature = "stdlib"))] pub fn init_stdlib(self) -> Self { - eprintln!("Warning: stdlib feature is not enabled. Standard library will not be available."); + eprintln!( + "Warning: stdlib feature is not enabled. Standard library will not be available." + ); self } /// Convenience method to set the debug level - /// + /// /// # Example /// ```no_run /// # use rustpython::InterpreterConfig; @@ -147,7 +149,7 @@ impl InterpreterConfig { } /// Convenience method to add a single path to the module search paths - /// + /// /// # Example /// ```no_run /// # use rustpython::InterpreterConfig; @@ -157,12 +159,15 @@ impl InterpreterConfig { /// .interpreter(); /// ``` pub fn add_path(mut self, path: impl Into) -> Self { - self.settings.get_or_insert_with(Default::default).path_list.push(path.into()); + self.settings + .get_or_insert_with(Default::default) + .path_list + .push(path.into()); self } /// Add multiple paths to the module search paths at once - /// + /// /// # Example /// ```no_run /// # use rustpython::InterpreterConfig; @@ -170,7 +175,7 @@ impl InterpreterConfig { /// .add_paths(vec!["Lib", ".", "custom_modules"]) /// .interpreter(); /// ``` - pub fn add_paths(mut self, paths: I) -> Self + pub fn add_paths(mut self, paths: I) -> Self where I: IntoIterator, S: Into, @@ -182,7 +187,7 @@ impl InterpreterConfig { } /// Initialize the standard library modules -/// +/// /// This function sets up both native modules and handles frozen/dynamic stdlib loading #[cfg(feature = "stdlib")] pub fn init_stdlib(vm: &mut VirtualMachine) { @@ -196,13 +201,13 @@ pub fn init_stdlib(vm: &mut VirtualMachine) { } /// Setup frozen standard library -/// +/// /// Used when the stdlib is compiled into the binary #[cfg(all(feature = "stdlib", feature = "freeze-stdlib"))] fn setup_frozen_stdlib(vm: &mut VirtualMachine) { vm.add_frozen(rustpython_pylib::FROZEN_STDLIB); - // FIXME: Remove this hack once sys._stdlib_dir is properly implemented + // FIXME: Remove this hack once sys._stdlib_dir is properly implemented // or _frozen_importlib doesn't depend on it anymore. // The assert ensures _stdlib_dir doesn't already exist before we set it assert!(vm.sys_module.get_attr("_stdlib_dir", vm).is_err()); @@ -216,7 +221,7 @@ fn setup_frozen_stdlib(vm: &mut VirtualMachine) { } /// Setup dynamic standard library loading from filesystem -/// +/// /// Used when the stdlib is loaded from disk at runtime #[cfg(all(feature = "stdlib", not(feature = "freeze-stdlib")))] fn setup_dynamic_stdlib(vm: &mut VirtualMachine) { @@ -233,7 +238,7 @@ fn setup_dynamic_stdlib(vm: &mut VirtualMachine) { } /// Collect standard library paths from build-time configuration -/// +/// /// Checks BUILDTIME_RUSTPYTHONPATH environment variable or uses default pylib path #[cfg(all(feature = "stdlib", not(feature = "freeze-stdlib")))] fn collect_stdlib_paths() -> Vec { @@ -281,9 +286,7 @@ mod tests { #[test] fn test_add_multiple_paths_sequential() { - let config = InterpreterConfig::new() - .add_path("path1") - .add_path("path2"); + let config = InterpreterConfig::new().add_path("path1").add_path("path2"); let settings = config.settings.unwrap(); assert_eq!(settings.path_list.len(), 2); } From ec9cc6ddc981d215108d049181e9601932a1cd02 Mon Sep 17 00:00:00 2001 From: "A.X.E.L" Date: Sun, 28 Dec 2025 14:34:22 +0100 Subject: [PATCH 3/6] Improve error message for non-Unicode paths --- src/interpreter.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/interpreter.rs b/src/interpreter.rs index 7fa2acef1f9..0600a6e0f44 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -248,7 +248,8 @@ fn collect_stdlib_paths() -> Vec { if let Some(paths) = option_env!("BUILDTIME_RUSTPYTHONPATH") { additional_paths.extend( crate::settings::split_paths(paths) - .map(|path| path.into_os_string().into_string().unwrap()), + .map(|path| path.into_os_string().into_string() + .unwrap_or_else(|_| panic!("BUILDTIME_RUSTPYTHONPATH isn't valid unicode"))), ) } else { #[cfg(feature = "rustpython-pylib")] From 7e7428b4ead0ae5e51a3182ea68596358efc90f7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 28 Dec 2025 13:34:47 +0000 Subject: [PATCH 4/6] Auto-format: cargo fmt --all --- src/interpreter.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/interpreter.rs b/src/interpreter.rs index 0600a6e0f44..28c15a386b2 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -246,11 +246,11 @@ fn collect_stdlib_paths() -> Vec { // BUILDTIME_RUSTPYTHONPATH should be set when distributing if let Some(paths) = option_env!("BUILDTIME_RUSTPYTHONPATH") { - additional_paths.extend( - crate::settings::split_paths(paths) - .map(|path| path.into_os_string().into_string() - .unwrap_or_else(|_| panic!("BUILDTIME_RUSTPYTHONPATH isn't valid unicode"))), - ) + additional_paths.extend(crate::settings::split_paths(paths).map(|path| { + path.into_os_string() + .into_string() + .unwrap_or_else(|_| panic!("BUILDTIME_RUSTPYTHONPATH isn't valid unicode")) + })) } else { #[cfg(feature = "rustpython-pylib")] additional_paths.push(rustpython_pylib::LIB_PATH.to_owned()) From 72b4343a14d42c32b41dc90018f6d965faae325a Mon Sep 17 00:00:00 2001 From: "A.X.E.L" Date: Mon, 29 Dec 2025 12:40:57 +0100 Subject: [PATCH 5/6] Use consistent make_module naming in all doc examples --- src/interpreter.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/interpreter.rs b/src/interpreter.rs index 0600a6e0f44..3258225d01d 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -34,16 +34,16 @@ pub type InitHook = Box; /// use rustpython::InterpreterConfig; /// use rustpython_vm::{VirtualMachine, PyRef, builtins::PyModule}; /// -/// fn make_custom_module(vm: &VirtualMachine) -> PyRef { +/// fn make_module(vm: &VirtualMachine) -> PyRef { /// // Your module implementation /// # todo!() /// } -/// +/// /// let interpreter = InterpreterConfig::new() /// .init_stdlib() /// .add_native_module( /// "your_module_name".to_owned(), -/// make_custom_module, +/// make_module, // ← use make_module /// ) /// .interpreter(); /// ``` @@ -98,9 +98,9 @@ impl InterpreterConfig { /// ```no_run /// # use rustpython::InterpreterConfig; /// # use rustpython_vm::{VirtualMachine, PyRef, builtins::PyModule}; - /// # fn my_module(vm: &VirtualMachine) -> PyRef { todo!() } + /// # fn make_module(vm: &VirtualMachine) -> PyRef { todo!() } /// let interpreter = InterpreterConfig::new() - /// .add_native_module("mymodule".to_owned(), my_module) + /// .add_native_module("mymodule".to_owned(), make_module) /// .interpreter(); /// ``` pub fn add_native_module( From c4d502acd230f28c147dc17074f9a18eb679075a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 29 Dec 2025 11:44:33 +0000 Subject: [PATCH 6/6] Auto-format: cargo fmt --all --- src/interpreter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interpreter.rs b/src/interpreter.rs index 9fb2a9a7b9c..d3878f4f952 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -38,7 +38,7 @@ pub type InitHook = Box; /// // Your module implementation /// # todo!() /// } -/// +/// /// let interpreter = InterpreterConfig::new() /// .init_stdlib() /// .add_native_module(