use crate::{
cache::CodeFileCache,
model::{CodeExecutor, CodeScriptExecutionResult, CommandExecutor, LanguageScript, RunCode},
python_runner::parse_import,
};
use anyhow::{Context, Result};
use log::{debug, error, info, warn};
use tokio::fs;
use tokio::process::Command;
#[derive(Default)]
pub struct PythonRunner;
const PYTHON_ACCELERATION_ADDRESS: &str = "https://mirrors.aliyun.com/pypi/simple";
impl RunCode for PythonRunner {
async fn run_with_params(
&self,
code: &str,
params: Option<serde_json::Value>,
timeout_seconds: Option<u64>,
) -> Result<CodeScriptExecutionResult> {
debug!("开始执行Python脚本...,执行参数: {params:?}");
let hash = CodeFileCache::obtain_code_hash(code);
let cache_exist =
CodeFileCache::check_code_file_cache_exisht(&hash, &LanguageScript::Python).await;
let run_code_script_file_tuple = if cache_exist {
let cache_code =
CodeFileCache::get_code_file_cache(&hash, &LanguageScript::Python).await;
debug!("从缓存中读取代码:hash值 {:?}", hash);
cache_code?
} else {
let dependencies = parse_import(code)?;
let wrapped_code = self.prepare_python_code(code, true);
CodeFileCache::save_code_file_cache(&hash, &wrapped_code, &LanguageScript::Python)
.await?;
let code_script_file_tuple =
CodeFileCache::get_code_file_cache(&hash, &LanguageScript::Python).await?;
let run_code_script_file_path = code_script_file_tuple.1.clone();
if !dependencies.is_empty() {
info!("正在添加依赖: {dependencies:?}");
let mut cmd = Command::new("uv");
cmd.arg("add")
.arg("--script")
.arg(&run_code_script_file_path);
cmd.arg("--default-index").arg(PYTHON_ACCELERATION_ADDRESS);
for dep in &dependencies {
cmd.arg(dep);
}
let cmd_str = format!("{:?}", cmd);
info!("uv命令字符串: {cmd_str}");
let cmd_output = match cmd.kill_on_drop(true).output().await {
Ok(output) => output,
Err(e) => {
error!("安装Python依赖失败: {e:?}");
error!("失败的命令: {cmd:?}");
return Err(e).context("Failed to add dependencies with uv");
}
};
let stdout = String::from_utf8_lossy(&cmd_output.stdout).to_string();
let stderr = String::from_utf8_lossy(&cmd_output.stderr).to_string();
info!("添加依赖结果 - stdout: {stdout}");
info!("添加依赖结果 - stderr: {stderr}");
if !cmd_output.status.success() {
warn!("添加依赖失败,状态码: {}", cmd_output.status);
}
}
debug!("创建脚本缓存:hash值 {:?}", hash);
code_script_file_tuple
};
let temp_path = run_code_script_file_tuple.1;
let mut execute_command = Command::new("uv");
execute_command
.arg("run")
.arg("-s") .arg("--default-index")
.arg(PYTHON_ACCELERATION_ADDRESS)
.arg(&temp_path)
.kill_on_drop(true);
let temp_input_path = if let Some(params) = params {
let params_json = serde_json::to_string(¶ms)?;
let temp_dir = tempfile::TempDir::new()?;
let temp_file_path = temp_dir.path().join("input_params.json");
std::fs::write(&temp_file_path, params_json.as_bytes())?;
std::mem::forget(temp_dir);
execute_command.env("INPUT_JSON_FILE", &temp_file_path);
debug!("使用临时文件传递参数,文件路径: {:?}", temp_file_path);
Some(temp_file_path)
} else {
execute_command.env("INPUT_JSON", "{}");
None
};
info!("执行命令: {:?}", execute_command);
let executor = match timeout_seconds {
Some(timeout) => CommandExecutor::with_timeout(execute_command.output(), timeout),
None => CommandExecutor::default(execute_command.output()),
};
let executor_result = executor.await;
let output = match executor_result {
Ok(cmd_result) => match cmd_result {
Ok(output) => output,
Err(e) => {
error!("Python命令执行失败: {e:?}");
return Err(e.into());
}
},
Err(e) => {
error!("Python任务执行异常: {e:?}");
return Err(e.into());
}
};
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
debug!("Python stdout: {stdout}");
debug!("Python stderr: {stderr}");
if let Some(temp_file_path) = temp_input_path {
let _ = fs::remove_file(&temp_file_path).await;
if let Some(parent) = temp_file_path.parent() {
let _ = fs::remove_dir(parent).await;
}
debug!("已删除临时文件: {:?}", temp_file_path);
}
CodeExecutor::parse_execution_output(&output.stdout, &output.stderr).await
}
}
impl PythonRunner {
fn prepare_python_code(&self, code: &str, show_logs: bool) -> String {
let show_logs_value = if show_logs { "True" } else { "False" };
let template = include_str!("../templates/python_template.py");
template
.replace("{{USER_CODE}}", code)
.replace("{{SHOW_LOGS}}", show_logs_value)
}
}