Ch 12 — Building the App
Ch 12 — Building the App
This is where everything clicks together. Aryan has variables, types, operators, strings, control flow, data structures, functions, classes, builtins, the standard library, psutil, and async HTTP. Chapter 12 assembles all of it into the final RAM Manager application — a full CLI tool with rich output, live updates, config file support, subprocess integration, and a versioned release history from v0.0 to v1.2.
Version History
| Version | What was added |
|---|---|
| v0.0 | Bare script — print(psutil.virtual_memory()) |
| v0.1 | Polling loop with threshold alert |
| v0.2 | Per-process table with formatted output |
| v0.3 | Config file (config.json) |
| v0.4 | Argparse CLI flags (--threshold, --interval) |
| v0.5 | click CLI with subcommands |
| v0.6 | rich styled tables |
| v0.7 | rich.live dashboard (live updating) |
| v0.8 | subprocess integration for ps / top |
| v0.9 | requests webhook alert |
| v1.0 | textual TUI app with panels |
| v1.1 | dataclasses + typed models throughout |
| v1.2 | Full capstone: async polling, history, reports |
click — CLI Framework
click turns functions into subcommands with @click.command() and typed options.
import click
@click.group()
def cli():
"""RAM Manager — monitor your system memory."""
pass
@cli.command()
@click.option("--threshold", default=80, help="Alert threshold (%).")
@click.option("--interval", default=1, help="Poll interval (seconds).")
@click.option("--count", default=10, help="Number of polls to run.")
def watch(threshold, interval, count):
"""Live polling mode."""
for tick in range(count):
pct = psutil.virtual_memory().percent
status = "WARN" if pct > threshold else "OK"
click.echo(f"[{status}] RAM: {pct:.1f}%")
time.sleep(interval)
@cli.command()
def report():
"""Generate a one-time snapshot report."""
# ... write CSV report
click.echo("Report saved.")
if __name__ == "__main__":
cli()rich — Beautiful Terminal Output
rich provides styled tables, panels, progress bars, and markup.
from rich.console import Console
from rich.table import Table
console = Console()
def print_process_table(snapshots):
table = Table(title="Top Processes by RAM", show_lines=True)
table.add_column("PID", style="dim", justify="right")
table.add_column("Name", style="bold")
table.add_column("RSS MB", style="cyan", justify="right")
table.add_column("Usage %", style="yellow", justify="right")
for snap in sorted(snapshots, key=lambda s: s.rss_mb, reverse=True)[:10]:
color = "red" if snap.pct > 80 else "green"
table.add_row(
str(snap.pid),
snap.name,
f"{snap.rss_mb:.1f}",
f"[{color}]{snap.pct:.1f}%[/{color}]",
)
console.print(table)rich.live — Live Updating Dashboard
Update a table in-place without scrolling the terminal.
from rich.live import Live
from rich.table import Table
import time
def make_table(snapshots) -> Table:
table = Table(title=f"RAM Live — {datetime.now():%H:%M:%S}")
# ... add columns and rows
return table
with Live(make_table([]), refresh_per_second=1) as live:
for _ in range(60):
snaps = get_current_snapshots()
live.update(make_table(snaps))
time.sleep(1)Config File
Load settings from JSON at startup, fall back to defaults.
from pathlib import Path
import json
DEFAULT_CONFIG = {
"threshold_pct": 80,
"poll_interval_s": 1,
"top_n": 10,
"log_path": str(Path.home() / ".ram_manager" / "ram.log"),
}
def load_config(path="config.json") -> dict:
cfg_path = Path(path)
if cfg_path.exists():
with cfg_path.open() as f:
user_cfg = json.load(f)
return {**DEFAULT_CONFIG, **user_cfg} # user overrides defaults
return DEFAULT_CONFIGsubprocess — Shell Integration
Run ps, lsof, or top and parse their output.
import subprocess
def get_open_file_count(pid: int) -> int:
result = subprocess.run(
["lsof", "-p", str(pid)],
capture_output=True,
text=True,
)
# Each line after the header is one open file
lines = result.stdout.strip().splitlines()
return max(0, len(lines) - 1)Always use a list of arguments (not a shell string) to avoid injection vulnerabilities.
argparse — Standard Library CLI
A lighter alternative to click for simple scripts.
import argparse
parser = argparse.ArgumentParser(description="RAM Manager")
parser.add_argument("--threshold", type=float, default=80.0)
parser.add_argument("--interval", type=int, default=1)
parser.add_argument("--output", type=str, default="report.csv")
args = parser.parse_args()
# args.threshold → 80.0
# args.interval → 1textual — Full TUI Application
textual builds full terminal UI apps with panels, widgets, and event loops.
from textual.app import App, ComposeResult
from textual.widgets import Header, Footer, DataTable
from textual.reactive import reactive
class RamManagerApp(App):
CSS_PATH = "ram_manager.tcss"
BINDINGS = [
("q", "quit", "Quit"),
("r", "refresh", "Refresh"),
]
def compose(self) -> ComposeResult:
yield Header()
yield DataTable()
yield Footer()
def on_mount(self) -> None:
table = self.query_one(DataTable)
table.add_columns("PID", "Name", "RSS MB", "%")
self.set_interval(1, self.refresh_data)
def refresh_data(self) -> None:
table = self.query_one(DataTable)
table.clear()
for proc in psutil.process_iter(["pid", "name", "memory_percent"]):
try:
table.add_row(
str(proc.pid),
proc.name()[:25],
f"{proc.memory_info().rss / 1024**2:.1f}",
f"{proc.memory_percent():.1f}",
)
except (psutil.NoSuchProcess, psutil.AccessDenied):
passApp Architecture — v1.2
flowchart TD
A[Entry Point: cli.py] --> B[click CLI group]
B --> C[watch subcommand]
B --> D[report subcommand]
B --> E[tui subcommand]
C --> F[load_config]
F --> G[psutil polling loop]
G --> H[ProcessSnapshot dataclasses]
H --> I{Threshold crossed?}
I -->|Yes| J[post_with_retry → Slack]
I -->|No| K[Append to history deque]
K --> L[rich.live update table]
L --> G
D --> M[write_report → CSV]
E --> N[textual RamManagerApp]Module Dependency Map
mindmap
root((RAM Manager v1.2))
CLI Layer
click subcommands
argparse fallback
UI Layer
rich tables
rich.live dashboard
textual TUI
Data Layer
psutil system reads
dataclasses models
collections.deque history
Config Layer
json config file
os.environ secrets
pathlib paths
Network Layer
requests webhook alert
httpx async polling
retry + backoff
Persistence Layer
csv reports
sqlite3 history db
logging structured logsKey Takeaways
clickprovides composable CLI subcommands with type-checked options — the go-to for Python CLIs.richtransforms plain terminal output into styled tables, panels, and markup with minimal code.rich.liveenables in-place updating dashboards — essential for a live RAM monitor.- Config files + environment variables + CLI flags form a three-tier config system: defaults → file → CLI wins.
subprocess.run()with a list argument safely integrates external system tools without shell injection risk.argparseis the standard library’s CLI parser — use it for lightweight scripts,clickfor complex apps.textualbuilds full TUI applications with widgets, event loops, and CSS-like styling.- The final RAM Manager is the sum of every concept in the course: data structures hold the snapshots, classes model them, functions process them, async fetches external data, and the UI layers display it all live.