Skip to main content

jorm/executor/
python.rs

1use crate::core::engine::TaskResult;
2use crate::core::{error::JormError, task::TaskType};
3use std::process::Stdio;
4use tokio::process::Command;
5use tokio::time::{timeout, Duration};
6
7pub struct PythonExecutor;
8
9impl Default for PythonExecutor {
10    fn default() -> Self {
11        Self::new()
12    }
13}
14
15impl PythonExecutor {
16    pub fn new() -> Self {
17        Self
18    }
19
20    pub async fn execute(
21        &self,
22        task_name: &str,
23        task_type: &TaskType,
24    ) -> Result<TaskResult, JormError> {
25        match task_type {
26            TaskType::Python {
27                script,
28                args,
29                working_dir,
30            } => {
31                self.execute_python_script(task_name, script, args.as_ref(), working_dir.as_deref())
32                    .await
33            }
34            _ => Err(JormError::ExecutionError(format!(
35                "Python executor cannot handle task type: {:?}",
36                task_type
37            ))),
38        }
39    }
40
41    async fn execute_python_script(
42        &self,
43        task_name: &str,
44        script: &str,
45        args: Option<&Vec<String>>,
46        working_dir: Option<&str>,
47    ) -> Result<TaskResult, JormError> {
48        // Create command - try python3 first, then python
49        let mut cmd = Command::new("python3");
50        cmd.arg(script)
51            .stdout(Stdio::piped())
52            .stderr(Stdio::piped());
53
54        // Add script arguments if provided
55        if let Some(script_args) = args {
56            cmd.args(script_args);
57        }
58
59        // Set working directory if specified
60        if let Some(dir) = working_dir {
61            cmd.current_dir(dir);
62        }
63
64        // Try python3 first
65        let timeout_duration = Duration::from_secs(60); // Python scripts might take longer
66
67        let result = timeout(timeout_duration, cmd.output()).await;
68
69        // If python3 fails, try python
70        let output = match result {
71            Ok(Ok(output)) => output,
72            Ok(Err(_)) | Err(_) => {
73                // Try with 'python' command
74                let mut cmd = Command::new("python");
75                cmd.arg(script)
76                    .stdout(Stdio::piped())
77                    .stderr(Stdio::piped());
78
79                if let Some(script_args) = args {
80                    cmd.args(script_args);
81                }
82
83                if let Some(dir) = working_dir {
84                    cmd.current_dir(dir);
85                }
86
87                match timeout(timeout_duration, cmd.output()).await {
88                    Ok(Ok(output)) => output,
89                    Ok(Err(e)) => {
90                        return Err(JormError::ExecutionError(format!(
91                            "Failed to execute Python script '{}': {}",
92                            script, e
93                        )));
94                    }
95                    Err(_) => {
96                        return Err(JormError::ExecutionError(format!(
97                            "Python script '{}' timed out after {} seconds",
98                            script,
99                            timeout_duration.as_secs()
100                        )));
101                    }
102                }
103            }
104        };
105
106        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
107        let stderr = String::from_utf8_lossy(&output.stderr).to_string();
108
109        let success = output.status.success();
110        let error = if !stderr.is_empty() && !success {
111            Some(stderr)
112        } else {
113            None
114        };
115
116        Ok(TaskResult {
117            task_name: task_name.to_string(),
118            success,
119            output: stdout,
120            error,
121        })
122    }
123}