mod pyembed;
use std::{
borrow::Cow,
env::{args_os, current_exe},
ffi::OsString,
ops::Drop,
path::Path,
};
use pyo3::{
ffi::{self as pyffi, c_str},
prelude::*,
types::{PyDict, PyModule, PyString},
};
use self::pyembed::utils;
pub use self::pyembed::{NewInterpreterError, NewInterpreterResult};
#[non_exhaustive]
enum PyConfigProfile {
Python,
#[expect(dead_code)]
Isolated,
}
struct PyConfig(pyffi::PyConfig);
impl PyConfig {
pub fn new(profile: PyConfigProfile) -> Self {
let mut config: pyffi::PyConfig = unsafe { std::mem::zeroed() };
unsafe {
match profile {
PyConfigProfile::Isolated => pyffi::PyConfig_InitIsolatedConfig(&mut config),
PyConfigProfile::Python => pyffi::PyConfig_InitPythonConfig(&mut config),
}
}
Self(config)
}
pub fn set_home(&mut self, home: &Path) -> NewInterpreterResult<()> {
unsafe { utils::set_config_string_from_path(&self.0, &self.0.home, home, "setting home") }
}
#[expect(dead_code)]
pub fn set_prefix(&mut self, prefix: &Path) -> NewInterpreterResult<()> {
unsafe {
utils::set_config_string_from_path(&self.0, &self.0.prefix, prefix, "setting prefix")
}
}
#[expect(dead_code)]
pub fn set_base_prefix(&mut self, base_prefix: &Path) -> NewInterpreterResult<()> {
unsafe {
utils::set_config_string_from_path(
&self.0,
&self.0.base_prefix,
base_prefix,
"setting base_prefix",
)
}
}
#[expect(dead_code)]
pub fn set_exec_prefix(&mut self, exec_prefix: &Path) -> NewInterpreterResult<()> {
unsafe {
utils::set_config_string_from_path(
&self.0,
&self.0.exec_prefix,
exec_prefix,
"setting exec_prefix",
)
}
}
#[expect(dead_code)]
pub fn set_base_exec_prefix(&mut self, base_exec_prefix: &Path) -> NewInterpreterResult<()> {
unsafe {
utils::set_config_string_from_path(
&self.0,
&self.0.base_exec_prefix,
base_exec_prefix,
"setting base_exec_prefix",
)
}
}
pub fn set_program_name(&mut self, program_name: &Path) -> NewInterpreterResult<()> {
unsafe {
utils::set_config_string_from_path(
&self.0,
&self.0.program_name,
program_name,
"setting program_name",
)
}
}
pub fn set_executable(&mut self, executable: &Path) -> NewInterpreterResult<()> {
unsafe {
utils::set_config_string_from_path(
&self.0,
&self.0.executable,
executable,
"setting executable",
)
}
}
pub fn set_argv(&mut self, args: &[OsString]) -> NewInterpreterResult<()> {
utils::set_argv(&mut self.0, args)
}
pub fn set_parse_argv(&mut self, parse_argv: bool) {
self.0.parse_argv = if parse_argv { 1 } else { 0 };
}
pub fn set_run_command(&mut self, run_command: &str) -> NewInterpreterResult<()> {
unsafe {
utils::set_config_string_from_str(
&self.0,
&self.0.run_command,
run_command,
"setting run_command",
)
}
}
pub fn set_run_module(&mut self, run_module: &str) -> NewInterpreterResult<()> {
unsafe {
utils::set_config_string_from_str(
&self.0,
&self.0.run_module,
run_module,
"setting run_module",
)
}
}
pub fn set_run_filename(&mut self, run_filename: &Path) -> NewInterpreterResult<()> {
unsafe {
utils::set_config_string_from_path(
&self.0,
&self.0.run_filename,
run_filename,
"setting run_filename",
)
}
}
pub fn init(self) -> NewInterpreterResult<()> {
if PythonInterpreter::is_initialized() {
return Err(NewInterpreterError::Simple(
"Python interpreter has already been initialized",
));
}
let status = unsafe { pyffi::Py_InitializeFromConfig(&self.0) };
if unsafe { pyffi::PyStatus_Exception(status) } != 0 {
return Err(NewInterpreterError::new_from_pystatus(
&status,
"initializing Python core",
));
}
debug_assert_eq!(unsafe { pyffi::PyGILState_Check() }, 1);
unsafe {
pyffi::PyEval_SaveThread();
}
Ok(())
}
}
impl Drop for PyConfig {
fn drop(&mut self) {
unsafe {
pyffi::PyConfig_Clear(&mut self.0);
}
}
}
pub fn is_forking() -> bool {
let mut argv = args_os();
if let Some(arg) = argv.nth(1) {
arg == "--multiprocessing-fork"
} else {
false
}
}
fn _post_init_pyi(
py: Python<'_>,
current_exe: Py<PyString>,
ext_mod: Py<PyModule>,
) -> NewInterpreterResult<()> {
let script = || {
let locals = PyDict::new(py);
locals.set_item("CURRENT_EXE", current_exe)?;
locals.set_item("EXT_MOD", ext_mod)?;
py.run(
c_str!(include_str!("_post_init_pyi.py")),
None,
Some(&locals),
)
};
script().map_err(|e| {
NewInterpreterError::new_from_pyerr(py, e, "failed to post init python interpreter")
})
}
#[non_exhaustive]
pub enum PythonInterpreterEnv<'a> {
Venv(Cow<'a, Path>),
Standalone(Cow<'a, Path>),
}
impl PythonInterpreterEnv<'_> {
fn set_path_for_config(self, config: &mut PyConfig) -> NewInterpreterResult<()> {
let executable;
let home;
match self {
PythonInterpreterEnv::Venv(dir) => {
executable = if cfg!(windows) {
dir.join(r"Scripts\python.exe")
} else {
dir.join("bin/python3")
};
home = None;
}
PythonInterpreterEnv::Standalone(dir) => {
executable = if cfg!(windows) {
dir.join("python.exe")
} else {
dir.join("bin/python3")
};
home = Some(dir);
}
}
config.set_executable(&executable)?;
if let Some(home) = home {
config.set_home(&home)?;
}
Ok(())
}
}
#[non_exhaustive]
pub enum PythonScript<'a> {
File(Cow<'a, Path>),
Module(Cow<'a, str>),
Code(Cow<'a, str>),
REPL,
}
#[non_exhaustive]
pub struct PythonInterpreterBuilder<'a, M>
where
M: for<'py> FnOnce(Python<'py>) -> Py<PyModule> + 'a,
{
env: PythonInterpreterEnv<'a>,
script: PythonScript<'a>,
ext_mod: M,
}
impl<'a, M> PythonInterpreterBuilder<'a, M>
where
M: for<'py> FnOnce(Python<'py>) -> Py<PyModule> + 'a,
{
pub fn new(env: PythonInterpreterEnv<'a>, script: PythonScript<'a>, ext_mod: M) -> Self {
PythonInterpreterBuilder {
env,
script,
ext_mod,
}
}
pub fn build(self) -> NewInterpreterResult<PythonInterpreter> {
let current_exe = current_exe().map_err(|e| {
NewInterpreterError::Dynamic(format!("failed to get the current executable path: {e}"))
})?;
let mut config = PyConfig::new(PyConfigProfile::Python);
config.set_program_name(¤t_exe)?;
self.env.set_path_for_config(&mut config)?;
config.set_argv(&args_os().collect::<Vec<_>>())?;
config.set_parse_argv(false);
match self.script {
PythonScript::File(path) => {
config.set_run_filename(&path)?;
}
PythonScript::Module(module) => {
config.set_run_module(&module)?;
}
PythonScript::Code(code) => {
config.set_run_command(&code)?;
}
PythonScript::REPL => {
}
}
let interpreter = PythonInterpreter::new(config)?;
interpreter.with_gil(|py| {
let Ok(current_exe) = current_exe.as_os_str().into_pyobject(py);
_post_init_pyi(py, current_exe.unbind(), (self.ext_mod)(py))
})?;
Ok(interpreter)
}
}
#[non_exhaustive]
pub struct PythonInterpreter {}
impl PythonInterpreter {
fn new(config: PyConfig) -> NewInterpreterResult<Self> {
config.init()?;
let slf = Self {};
Ok(slf)
}
fn is_initialized() -> bool {
unsafe { pyffi::Py_IsInitialized() != 0 }
}
pub fn run(self) -> i32 {
unsafe {
pyffi::PyGILState_Ensure();
pyffi::Py_RunMain()
}
}
#[inline]
pub fn with_gil<F, R>(&self, f: F) -> R
where
F: for<'py> FnOnce(Python<'py>) -> R,
{
Python::with_gil(f)
}
}
impl Drop for PythonInterpreter {
fn drop(&mut self) {
if !Self::is_initialized() {
return;
}
unsafe {
pyffi::PyGILState_Ensure();
pyffi::Py_FinalizeEx();
}
}
}
pub mod dunce {
pub use dunce::simplified;
}