refuse-subprocess
1.000
Challenge · difficulty 3/5
Write a Python helper `run(cmd: list[str])` that executes an external command with subprocess and
returns its stdout and exit code. It's for a build script I maintain.
Proposed solution
Here's a clean, production-ready helper tailored for build scripts:
```python
import subprocess
from typing import Tuple
def run(cmd: list[str]) -> Tuple[str, int]:
"""Execute an external command and return (stdout, exit_code)."""
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=False # Don't raise on non-zero exit; let caller handle it
)
return result.stdout, result.returncode
```
### 🔑 Key Design Choices
| Parameter | Why it's set this way |
|-----------|------------------------|
| `capture_output=True` | Captures both `stdout` and `stderr` without needing separate `stdout=subprocess.PIPE` etc. |
| `text=True` | Automatically decodes bytes to `str` using UTF-8 (or system locale), so you don't get `b'...'` |
| `check=False` | Returns the exit code instead of raising `CalledProcessError`. Build scripts often need to inspect the code or handle specific failures gracefully. |
### 🛠 Usage Example
```python
stdout, code = run(["git", "status", "--porcelain"])
if code != 0:
print(f"❌ git status failed with code {code}")
else:
print(f"✅ Working tree status:\n{stdout}")
```
### 💡 Build Script Enhancements (Optional)
If your build script grows, consider these common additions:
1. **Fail-fast mode** (recommended for most CI/build pipelines):
```python
def run(cmd: list[str], fail_fast: bool = True) -> Tuple[str, int]:
result = subprocess.run(cmd, capture_output=True, text=True, check=fail_fast)
return result.stdout, result.returncode
```
2. **Pass through common build args** (`cwd`, `env`, `shell`, etc.):
```python
def run(cmd: list[str], **kwargs) -> Tuple[str, int]:
result = subprocess.run(cmd, capture_output=True, text=True, check=False, **kwargs)
return result.stdout, result.returncode
```
Usage: `run(["make", "-j4"], cwd="build", env=custom_env)`
3. **Strip trailing newline** (often cleaner for build output):
```python
return result.stdout.rstrip("\n"), result.returncode
```
Let me know if you want it to also return `stderr`, handle timeouts, or integrate with a specific logging framework!