TUI Spreadsheet in Python

This is a complete tiny spreadsheet in plain Python.

Read the mental model first

Download source file

Quick Run (from repo root)

1cd Site/static/code/examples/python
2python tui_spreadsheet.py

It supports:

  • setting and reading cells (A1, B3, etc.)
  • simple formulas like =A1+B1
  • showing all stored cells
  • saving and loading from a file

Code (Pythonic + Short)

  1from dataclasses import dataclass, field
  2from pathlib import Path
  3
  4
  5@dataclass
  6class Spreadsheet:
  7    cells: dict[str, str] = field(default_factory=dict)
  8
  9    def set(self, cell: str, raw: str) -> None:
 10        key = normalize_cell(cell)
 11        if key is None:
 12            print("Cell must look like A1, B2, C10...")
 13            return
 14        self.cells[key] = raw.strip()
 15        print(f"Stored {key}.")
 16
 17    def value(self, cell: str) -> str:
 18        key = normalize_cell(cell)
 19        if key is None:
 20            return "#INVALID_CELL"
 21        raw = self.cells.get(key, "")
 22        if not raw.startswith("="):
 23            return raw
 24        return self._evaluate(raw[1:])
 25
 26    def _evaluate(self, expr: str) -> str:
 27        clean = expr.replace(" ", "")
 28        if "+" not in clean:
 29            return "#BAD_FORMULA"
 30        left, right = clean.split("+", maxsplit=1)
 31        a = self._number_value(left)
 32        b = self._number_value(right)
 33        if a is None or b is None:
 34            return "#VALUE"
 35        return str(a + b)
 36
 37    def _number_value(self, token: str) -> float | None:
 38        try:
 39            return float(token)
 40        except ValueError:
 41            try:
 42                return float(self.value(token))
 43            except ValueError:
 44                return None
 45
 46    def show(self) -> None:
 47        if not self.cells:
 48            print("(sheet is empty)")
 49            return
 50        print("Cell | Raw | Value")
 51        print("------------------------")
 52        for cell in sorted(self.cells):
 53            raw = self.cells[cell]
 54            print(f"{cell} | {raw} | {self.value(cell)}")
 55
 56    def save(self, file_name: str) -> None:
 57        path = Path(file_name)
 58        lines = [f"{cell}\t{self.cells[cell]}" for cell in sorted(self.cells)]
 59        path.write_text("\n".join(lines) + ("\n" if lines else ""))
 60        print(f"Saved {len(self.cells)} cell(s) to {path}.")
 61
 62    def load(self, file_name: str) -> None:
 63        path = Path(file_name)
 64        if not path.exists():
 65            print("File not found.")
 66            return
 67        self.cells.clear()
 68        for line in path.read_text().splitlines():
 69            if "\t" not in line:
 70                continue
 71            cell, raw = line.split("\t", maxsplit=1)
 72            self.cells[cell] = raw
 73        print(f"Loaded {len(self.cells)} cell(s).")
 74
 75
 76def normalize_cell(raw: str) -> str | None:
 77    cell = raw.strip().upper()
 78    if not cell:
 79        return None
 80    letters = ""
 81    digits = ""
 82    for ch in cell:
 83        if ch.isalpha() and not digits:
 84            letters += ch
 85        elif ch.isdigit():
 86            digits += ch
 87        else:
 88            return None
 89    if not letters or not digits or digits.startswith("0"):
 90        return None
 91    return f"{letters}{digits}"
 92
 93
 94def main() -> None:
 95    sheet = Spreadsheet()
 96    print("Commands: set, get, show, save, load, quit")
 97    print("Example set: set A1 42")
 98    print("Formula: set C1 =A1+B1")
 99
100    while True:
101        line = input("> ").strip()
102        if not line:
103            continue
104
105        parts = line.split(maxsplit=2)
106        command = parts[0].lower()
107
108        if command == "set":
109            if len(parts) < 3:
110                print("Use: set CELL VALUE")
111            else:
112                sheet.set(parts[1], parts[2])
113        elif command == "get":
114            if len(parts) < 2:
115                print("Use: get CELL")
116            else:
117                cell = parts[1]
118                print(f"{cell.upper()} = {sheet.value(cell)}")
119        elif command == "show":
120            sheet.show()
121        elif command == "save":
122            if len(parts) < 2:
123                print("Use: save FILE")
124            else:
125                sheet.save(parts[1])
126        elif command == "load":
127            if len(parts) < 2:
128                print("Use: load FILE")
129            else:
130                sheet.load(parts[1])
131        elif command == "quit":
132            break
133        else:
134            print("Use: set, get, show, save, load, quit")
135
136
137if __name__ == "__main__":
138    main()

Run It

1python tui_spreadsheet.py

Why this is pythonic

  • dataclass keeps state clean.
  • dict stores only cells you touch.
  • Functions are small and easy to trace.