# PYTHON_SCRIPT_RUNNER
Execute Python scripts from Rust with automatic project-relative path resolution, retry logic, and real-time output streaming.
## Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
python_script_runner = "0.1"
```
Or use local path:
```toml
python_script_runner = { path = "path/to/python_script_runner" }
```
## Quick Start
### Set environment variables in .env
```bash
PROJECT_DIRECTORY=/path/to/your/project
```
### Import and use
```rust
use python_script_runner::python_script_runner::{PythonExecutor, run_python_script};
let executor = PythonExecutor::new();
executor.execute_script("scripts/my_script.py").await?;
run_python_script("/absolute/path/to/script.py").await?;
```
## Configuration
### Environment Variables
| `PROJECT_DIRECTORY` | Yes | — | Root directory for script path resolution |
## Key Features
1. **Automated Python Script Execution** — Runs Python scripts via `tokio::process::Command` with async output capture — non-blocking
2. **Robust Retry Mechanism** — 3-attempt retry loop with fixed 3-second delay between attempts — covers transient failures (interpreter startup, temp file locks)
3. **Real-Time Output Streaming** — Captures and writes both stdout and stderr via `tokio::io` async writers — operator sees script progress immediately
4. **Project-Aware Path Management** — Resolves relative paths using `PROJECT_DIRECTORY` environment variable — portable across machines and CI
5. **Early File Validation** — Pre-validates script existence before entering the retry loop — fails fast rather than waiting 9 seconds for a missing file
## API Reference
### PythonExecutor::new() -> Result<PythonExecutor>
Create a new executor, loading `PROJECT_DIRECTORY` from `.env`.
- Returns: `Ok(PythonExecutor)` on success
### PythonExecutor::execute_script(&self, relative_path: &str) -> Result<()>
Execute a Python script given a project-relative path.
- `relative_path`: Path relative to `PROJECT_DIRECTORY` (e.g. "scripts/my_script.py")
- Returns: `Ok(())` on success
### run_python_script(script_path: &str) -> Result<()>
Execute a Python script at the given absolute path with retry and output streaming.
- `script_path`: Absolute path to the Python script
- Returns: `Ok(())` on success
## Output
Both stdout and stderr are streamed to the respective streams in real-time:
- stdout: Written to the executor's stdout
- stderr: Written to the executor's stderr
The script exit code is captured and returned as part of the `Result`.
## Error Handling
The library returns `anyhow::Result<T>`:
- `anyhow::anyhow!("PROJECT_DIRECTORY not set")` if not set
- `anyhow::anyhow!("Script file not found")` if the script doesn't exist
- `anyhow::anyhow!("Python interpreter not found")` if `python` or `python3` is not available
- `anyhow::anyhow!("Script execution failed with exit code N")` if the script fails
## Retry Policy
Script execution uses a 3-attempt retry loop:
- **Max attempts**: 3
- **Delay**: Fixed 3-second delay between attempts
- **Retryable**: All execution failures
- **Not retried**: Missing script file (fail fast)
**Note:** Performance is dominated by the external Python interpreter and script execution time. In-crate benchmarks are not meaningful.
## Test Coverage
```bash
cargo test --lib
```
| `PythonExecutor::new()` | 1 | 3 | success, missing PROJECT_DIRECTORY, missing .env |
| `execute_script()` | 1 | 4 | relative path, absolute path, retry on failure, output streaming |
| `run_python_script()` | 1 | 3 | success, missing script, python not found |
| Path resolution | 2 | 2 | relative path construction, fallback |
**Note:** Tests that require a Python interpreter will skip gracefully if `python`/`python3` is not available on the system PATH.
## Dependencies
```toml
[dependencies]
dotenvy = "0.15"
tokio = { version = "1", features = ["full"] }
anyhow = "1.0"
```