🔄 Quick Recap (Day 26)

  • You met three popular frameworks (Flask, Django, FastAPI) and built tiny apps in each.

🎯 What You’ll Learn Today

  1. Why automation matters and common use cases.

  2. How to run external commands with subprocess.run (capture output, timeouts, exit codes).

  3. How to schedule scripts on macOS/Linux with cron.

  4. How to schedule scripts on Windows with Task Scheduler (via schtasks).

  5. Practical tips: absolute paths, logs, virtual environments.

📖 Why Automate?

Automation saves time and prevents mistakes. Typical tasks:

  • Nightly reports or cleanup jobs.

  • Periodic backups or data pulls.

  • Checking service health and logging results.

📖 Running Commands with subprocess

subprocess.run lets your Python script call other programs and capture results.

import subprocess

# Basic call (raises no error on non‑zero exit)
res = subprocess.run(["python", "-V"], capture_output=True, text=True)
print(res.returncode)   # 0 on success
print(res.stdout.strip())

# Safer: fail fast and set timeout
try:
    res = subprocess.run(["python", "-V"], capture_output=True, text=True,
                         check=True, timeout=10)
    print("OK:", res.stdout.strip())
except subprocess.CalledProcessError as e:
    print("Command failed:", e)
except subprocess.TimeoutExpired:
    print("Command timed out")

Tips

  • Prefer explicit lists (no shell) for portability and safety.

  • Use sys.executable to call the exact Python of your virtualenv.

  • Log returncode, stdout, and stderr to diagnose failures.

📖 Scheduling on macOS/Linux with cron

cron runs commands on a schedule.

  1. Open your crontab:

crontab -e
  1. Add a line in the format min hour day month weekday command.

Examples

  • Every day at 09:00:

    0 9 * * * /full/path/to/venv/bin/python /full/path/to/daily_report.py
  • Every 5 minutes:

    */5 * * * * /full/path/to/venv/bin/python /full/path/to/daily_report.py

Notes

  • Always use absolute paths to Python and your script.

  • Environments are minimal in cron; rely on full paths rather than PATH.

  • Redirect output if needed: ... >> /full/path/logs/cron.log 2>&1.

📖 Scheduling on Windows with Task Scheduler

Use the GUI Task Scheduler or the schtasks command.

Command‑line example (run daily at 09:00):

schtasks /Create /SC DAILY /ST 09:00 /TN "DailyReport" ^
 /TR "\"C:\\Path\\to\\python.exe\" \"C:\\Path\\to\\daily_report.py\"" /F

Tips

  • Use the full path to your virtualenv Python (e.g., C:\path\env\Scripts\python.exe).

  • If you don’t want a console window, you can use pythonw.exe for GUI‑less scripts.

  • In Task properties, set Start in to your project folder if your script uses relative paths.

🧩 Example Script: daily_report.py

A simple job that logs timestamp + your Python and pip versions. Cross‑platform and virtualenv‑friendly.

# daily_report.py
from __future__ import annotations
import sys, subprocess
from datetime import datetime
from pathlib import Path

LOG_DIR = Path(__file__).with_suffix("").parent / "logs"
LOG_DIR.mkdir(parents=True, exist_ok=True)
LOG_FILE = LOG_DIR / "daily_report.log"


def run_cmd(args: list[str], timeout: int = 10) -> tuple[int, str, str]:
    """Run a command, capture output, return (code, stdout, stderr)."""
    try:
        r = subprocess.run(args, capture_output=True, text=True, timeout=timeout)
        return r.returncode, r.stdout.strip(), r.stderr.strip()
    except subprocess.TimeoutExpired:
        return 124, "", "timeout"


def main() -> None:
    py = sys.executable  # current interpreter (works in venvs)

    code_v, py_v, py_err = run_cmd([py, "-V"])  # e.g., "Python 3.11.x"
    code_p, pip_v, pip_err = run_cmd([py, "-m", "pip", "--version"])  # e.g., "pip 24.x ..."

    ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    status = "OK" if code_v == 0 and code_p == 0 else "ERROR"

    line = f"{ts} | {status} | {py_v or py_err} | {pip_v or pip_err}"
    LOG_FILE.write_text((LOG_FILE.read_text() if LOG_FILE.exists() else "") + line + "\n")
    print(f"Wrote: {LOG_FILE}")


if __name__ == "__main__":
    main()

Run once to test:

python daily_report.py

Sample output (terminal):

Wrote: /full/path/logs/daily_report.log

Sample log lines (logs/daily_report.log):

2025-08-12 09:00:00 | OK | Python 3.11.9 | pip 24.0 from ...
2025-08-13 09:00:00 | OK | Python 3.11.9 | pip 24.0 from ...

🧙‍♂️ Take the Wand and Try Yourself

Goal: Schedule daily_report.py to run automatically and verify the logs.

  1. Save daily_report.py in your project folder and run it once manually.

  2. macOS/Linux: add a cron entry to run at 09:00 daily (use your absolute paths).

  3. Windows: create a Task Scheduler job at 09:00 daily using your virtualenv python.exe.

  4. Wait for the next run (or change the schedule to every 5 minutes for testing).

  5. Open logs/daily_report.log and check a new line was appended.

Expected results:

  • Terminal shows Wrote: /.../logs/daily_report.log when run manually.

  • The log file contains a fresh timestamped line after the scheduled time.

  • If a command fails or times out, the line shows ERROR and the captured stderr.

Up next: Day 28: Mini Project Kickoff — define requirements, architecture, and set up your repo.

Keep Reading