Skip to main content

Execute as Python Script

Run marimo notebooks as standard Python scripts from the command line. This is ideal for automation, batch processing, CI/CD pipelines, and workflows that produce side effects like writing to disk or sending notifications.

Basic Execution

Run any marimo notebook as a Python script:
python my_notebook.py
When executed as a script:
  • All cells run in dependency order
  • Outputs go to stdout/stderr
  • UI elements are not interactive
  • The script exits when execution completes
Unlike marimo run, which starts a web server, running as a script executes the notebook once and exits. Perfect for cron jobs, automated reports, and data pipelines.

Why Run as a Script?

Use script execution when:
  • Automating workflows: Scheduled data processing, ETL jobs, report generation
  • CI/CD pipelines: Testing, validation, building artifacts
  • Batch processing: Process files, train models, generate outputs
  • Command-line tools: Interactive CLI applications with argparse
  • System integration: Call from other programs, shell scripts, or schedulers

Command-Line Arguments

Using argparse

The recommended way to handle arguments uses Python’s built-in argparse:
import marimo as mo
import sys
import argparse

# Define arguments
if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Process dataset")
    parser.add_argument("--input", required=True, help="Input file path")
    parser.add_argument("--output", required=True, help="Output file path")
    parser.add_argument("--verbose", action="store_true", help="Verbose output")
    args = parser.parse_args()
else:
    # Default values when running as notebook
    class Args:
        input = "data/sample.csv"
        output = "results/output.csv"
        verbose = False
    args = Args()
Run it:
python process_data.py --input data.csv --output results.csv --verbose

Using simple-parsing

For more complex configurations, use simple-parsing:
import marimo as mo
from dataclasses import dataclass
import sys

try:
    from simple_parsing import ArgumentParser
except ImportError:
    mo.md("Install simple-parsing: pip install simple-parsing")
    raise

@dataclass
class Config:
    """Training configuration"""
    learning_rate: float = 1e-4
    epochs: int = 100
    batch_size: int = 32
    model_path: str = "model.pkl"

if __name__ == "__main__":
    parser = ArgumentParser()
    parser.add_arguments(Config, dest="config")
    args = parser.parse_args()
    config = args.config
else:
    config = Config()  # Use defaults in notebook
Run it:
python train.py --learning_rate 0.001 --epochs 50 --batch_size 64

Using mo.cli_args()

marimo provides a lightweight argument parser:
import marimo as mo

args = mo.cli_args()
if args:
    dataset = args.get("dataset", "default")
    year = int(args.get("year", 2024))
    debug = args.get("debug", False)
else:
    # Defaults for notebook mode
    dataset = "default"
    year = 2024
    debug = False
Run it:
python script.py -- --dataset sales --year 2024 --debug
mo.cli_args() does basic type inference but doesn’t provide argument validation or help text. For production scripts, use argparse or simple-parsing.

Parameterization Patterns

Environment Variables

Use environment variables for configuration:
import os
import marimo as mo

# Read from environment with defaults
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///local.db")
API_KEY = os.getenv("API_KEY", "")
DEBUG = os.getenv("DEBUG", "false").lower() == "true"

if not API_KEY:
    mo.md("⚠️ **Warning**: API_KEY not set").callout(kind="warn")
Run it:
DATABASE_URL=postgresql://prod API_KEY=secret python script.py

Configuration Files

Load parameters from JSON, YAML, or TOML:
import marimo as mo
import json
import sys
from pathlib import Path

if __name__ == "__main__":
    config_path = sys.argv[1] if len(sys.argv) > 1 else "config.json"
else:
    config_path = "config.json"

with open(config_path) as f:
    config = json.load(f)

mo.md(f"Loaded config from {config_path}")
Run it:
python script.py production.json

Conditional Logic

Detect execution mode and adapt behavior:
import marimo as mo
import sys

# Detect if running as script
is_script_mode = __name__ == "__main__"

if is_script_mode:
    # Script mode: minimal output
    def log(msg):
        print(msg)
else:
    # Notebook mode: rich output
    def log(msg):
        return mo.md(msg)

# Use throughout notebook
log("Processing started...")

Output and Side Effects

Writing Files

import marimo as mo
import pandas as pd
from pathlib import Path

# Process data
df = pd.read_csv("input.csv")
results = process(df)

# Write outputs
output_dir = Path("results")
output_dir.mkdir(exist_ok=True)

results.to_csv(output_dir / "processed.csv", index=False)
results.to_parquet(output_dir / "processed.parquet")

mo.md(f"✅ Saved {len(results)} rows to {output_dir}")

Console Output

Print statements appear in the terminal:
import marimo as mo

print("Starting processing...")
for i in range(5):
    print(f"Processing item {i+1}/5")
    # Do work
    
print("✅ Complete!")
mo.md("Processing finished")
Output:
Starting processing...
Processing item 1/5
Processing item 2/5
...
✅ Complete!

Exit Codes

Return meaningful exit codes for automation:
import marimo as mo
import sys

try:
    # Process data
    results = dangerous_operation()
    if not validate(results):
        print("Validation failed!", file=sys.stderr)
        sys.exit(1)
    print(f"Success! Processed {len(results)} items")
    sys.exit(0)
except Exception as e:
    print(f"Error: {e}", file=sys.stderr)
    sys.exit(1)
Check exit codes in bash:
python script.py
if [ $? -eq 0 ]; then
    echo "Success"
else
    echo "Failed"
    exit 1
fi

Integration with Workflows

Cron Jobs

Schedule regular execution:
# Run daily at 2 AM
0 2 * * * cd /path/to/project && python daily_report.py >> logs/$(date +\%Y-\%m-\%d).log 2>&1

GitHub Actions

Integrate with CI/CD:
name: Run Analysis
on:
  schedule:
    - cron: '0 0 * * *'  # Daily at midnight
  workflow_dispatch:

jobs:
  analyze:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      - run: pip install marimo pandas
      - run: python analysis.py --output results.csv
      - uses: actions/upload-artifact@v3
        with:
          name: results
          path: results.csv

Shell Scripts

Orchestrate multiple notebooks:
#!/bin/bash
set -e  # Exit on error

echo "Running ETL pipeline..."

python 01_extract.py --source production
python 02_transform.py --input data/raw --output data/clean
python 03_load.py --target warehouse
python 04_report.py --email [email protected]

echo "✅ Pipeline complete"

Python Subprocess

Call from other Python code:
import subprocess
import sys

result = subprocess.run(
    [sys.executable, "notebook.py", "--", "--param", "value"],
    capture_output=True,
    text=True,
    check=False
)

if result.returncode == 0:
    print("Success:", result.stdout)
else:
    print("Failed:", result.stderr)
    sys.exit(1)

Validation Before Execution

Check notebooks for issues before running:
# Lint notebook
marimo check script.py

# Run only if check passes
marimo check script.py && python script.py
The marimo check command validates:
  • Multiple definition errors
  • Delete-nonlocal errors
  • Cycles in the dependency graph
  • Other common issues
See the Lint Rules guide for details.

Export with Execution

Combine execution with export to HTML:
# Execute and save HTML output
marimo export html notebook.py -o output.html

# With arguments
marimo export html report.py -o report.html -- --year 2024 --quarter Q1
This runs the notebook and captures all outputs in the HTML file.

Performance Considerations

Optimize script execution:
  1. Cache expensive computations: Use @functools.cache or persist results to disk
  2. Process in chunks: For large datasets, use batch processing
  3. Parallelize: Use multiprocessing or joblib for CPU-bound tasks
  4. Profile first: Use python -m cProfile script.py to identify bottlenecks
  5. Minimize dependencies: Import only what’s needed in each cell

Debugging Scripts

Use Python’s debugger:
# Run with debugger
python -m pdb script.py

# Or add breakpoint in code
import marimo as mo

# Your code
breakpoint()  # Execution pauses here
# More code

Examples

Daily Report Generator

import marimo as mo
import pandas as pd
from datetime import datetime
import argparse

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--email", required=True)
    parser.add_argument("--date", default=datetime.now().strftime("%Y-%m-%d"))
    args = parser.parse_args()
else:
    class Args:
        email = "[email protected]"
        date = datetime.now().strftime("%Y-%m-%d")
    args = Args()

# Load and process data
df = pd.read_sql(f"SELECT * FROM sales WHERE date = '{args.date}'", conn)
summary = df.groupby('region')['revenue'].sum()

# Generate and send report
report = mo.md(f"""
# Daily Sales Report - {args.date}
{mo.ui.table(summary)}
""")

if __name__ == "__main__":
    send_email(args.email, report.text)
    print(f"✅ Report sent to {args.email}")
Run daily:
python daily_report.py --email [email protected]

Model Training Pipeline

# Train model with custom parameters
python train_model.py -- --lr 0.001 --epochs 100 --gpu

Data Validation

# Validate data and exit with status code
python validate_data.py --input data.csv || exit 1

Best Practices

For production scripts:
  1. Use argparse for clear argument definitions and help text
  2. Validate inputs before processing
  3. Handle errors gracefully with try/except
  4. Log important events to files or monitoring systems
  5. Return meaningful exit codes (0 for success, non-zero for errors)
  6. Make scripts idempotent so they can safely re-run
  7. Test in notebook mode first before deploying as script

Next Steps

Deploy as App

Run notebooks as interactive web applications

CLI Arguments

Advanced command-line argument handling

Export Formats

Export notebooks to HTML, PDF, and more

CI/CD Integration

Deploy scripts in automated workflows

Build docs developers (and LLMs) love