A modern, feature-rich Python expense tracker with a CLI, interactive REPL, Flask web dashboard, SQLite storage, recurring expenses, budgets, multiple wallets, multi-currency support, financial intelligence, currency conversion, reporting, and JSON import/export.
Built to be simple enough for the terminal while providing a full web interface for day-to-day expense management.
Track your spending from the terminal or through a beautiful responsive web dashboard.
π Current release line: v0.5.0 β Financial Intelligence. The project extends the v0.4.x feature set with analytics, trends, comparisons, and dashboard intelligence while preserving the existing CLI, web, wallet, currency, budget, recurring-expense, and import/export workflows.
- π Project Overview
- πΈ Preview
- β¨ Features
- π οΈ Tech Stack
- π Project Structure
- π Installation
- π CLI Usage
- π Web Interface
- ποΈ Architecture
- πΎ Database Schema
- π§ͺ Running Tests
- π©Ή Troubleshooting
- π οΈ Development
- πΊοΈ Roadmap
- π€ Contributing
- π License
- π FAQ
- β Show Your Support
| π Language | Python 3.10+ |
| π Web Framework | Flask 3.x |
| πΎ Database | SQLite |
| π₯ Interface | CLI + Interactive REPL + Web |
| π± Responsive | β |
| π§ͺ Tested | Pytest |
| π License | MIT |
βββββββββββββββββββββββ Summary β 2026-06 βββββββββββββββββββββββ
Total: 2687.49 across 5 expenses
Budget: 3000.00 Remaining: 312.51
By Category
ββββββββββββββββββ¬ββββββββ¬ββββββββββ¬βββββββ
β Category β Count β Total β % β
ββββββββββββββββββΌββββββββΌββββββββββΌβββββββ€
β Housing β 1 β 2500.00 β 93.0 β
β Food β 2 β 132.50 β 4.9 β
β Transport β 1 β 45.00 β 1.7 β
β Entertainment β 1 β 9.99 β 0.4 β
ββββββββββββββββββ΄ββββββββ΄ββββββββββ΄βββββββ
ββββββββββββββββββββ¬βββββββββββββββββββββββββ¬ββββββββββββββββββ
β Total β 2026-06 β Budget remaining β Top category β
β 2,687.49 β 312.51 β Housing β
β 5 expenses β ββββββββββββββ 89% β 2,500.00 β
ββββββββββββββββββββ΄βββββββββββββββββββββββββ΄ββββββββββββββββββ
Per-category budgets
ββββββββββββββ¬ββββββββββ¬ββββββββββ¬βββββββββββ¬βββββββββββββββββββββ
β Category β Budget β Spent β Remainingβ Usage β
ββββββββββββββΌββββββββββΌββββββββββΌβββββββββββΌβββββββββββββββββββββ€
β π Food β 300.00 β 132.50 β 167.50 β ββββββββββ 44% β
β π Transp. β 150.00 β 45.00 β 105.00 β ββββββββββ 30% β
β π¬ Entert. β 50.00 β 9.99 β 40.01 β ββββββββββ 20% β
ββββββββββββββ΄ββββββββββ΄ββββββββββ΄βββββββββββ΄βββββββββββββββββββββ
- β Add / Edit / Delete expenses with description, amount, date, and category
- π·οΈ Manage categories β create custom ones with your own colors
- π Filter & search by date range and category
- π΅ Set monthly budgets β overall or per-category
- π Monthly summary with totals, counts, percentages, and budget tracking
- π§ Financial intelligence with 12-month trends, month-over-month changes, daily averages, active days, top categories, and top expenses
- π€ Export to CSV for spreadsheet analysis or backup
- ποΈ Local SQLite β no servers, no cloud, your data stays on your machine
- π¨ Beautiful terminal UI powered by Rich
- π Visualize spending as a horizontal bar chart (PNG via matplotlib)
- π§ͺ Fully tested with pytest (6 tests, all passing)
- π₯οΈ Single-page application β Dashboard, Expenses, Categories, Reports, Budget
- π Interactive charts powered by Chart.js (bar + doughnut)
- π― Per-category budget management with progress bars
- π Modal forms with validation and toast notifications
- π± Responsive layout β works on phone, tablet, desktop
- π Same SQLite database β CLI and web share data seamlessly
- Add, edit, delete, and list expenses
- Expense descriptions and amounts
- Date-based expense tracking
- Category support
- Search and filtering
- Monthly expense summaries
- CSV export
- Wallet assignment
- Per-expense currency support
Manage expenses across multiple wallets/accounts.
- Create multiple wallets
- Assign a currency to each wallet
- Assign expenses to specific wallets
- Update wallet names and currencies
- Delete wallets
- View wallet-specific information
- Default Main Wallet for existing installations
Track expenses and wallets using different currencies.
- Multiple currency codes per wallet and expense
- Cached exchange rates
- Manual exchange-rate entry
- Live rate resolution when a pair is missing
- Currency conversion
- Inverse-rate fallback
- Currency rate API endpoints
- Currency-aware CSV exports
The v0.5.0 Financial Intelligence layer builds on the existing reporting system and adds a dedicated analytics view without removing or changing the existing summary workflow.
- π 12-month monthly trend β view spending across the previous year
βοΈ Month-over-month comparison β compare the selected month with the previous month- π Absolute change β see how much spending increased or decreased
- π Percentage change β understand the relative month-over-month movement
- π Daily average β average spending across the selected month
- ποΈ Active spending days β identify how many days contained transactions
- π·οΈ Top categories β identify the largest spending categories
- π³ Top expenses β identify the largest individual transactions
- π Wallet-aware analysis β restrict analytics to a selected wallet
- π± Currency-aware analysis β calculate analytics in a requested reporting currency
- π REST API β consume intelligence data programmatically
- π Dashboard metrics β surface the most useful indicators directly on the web dashboard
GET /api/reports/intelligence?month=2026-06¤cy=USD
Optional wallet filtering can be supplied with wallet_id.
The endpoint is intentionally additive: the existing monthly summary API remains available and continues to provide the original report structure.
{
"month": "2026-06",
"currency": "USD",
"total": 2687.49,
"previous_total": 2510.20,
"change": 177.29,
"change_pct": 7.06,
"transaction_count": 5,
"active_days": 4,
"daily_average": 89.58,
"top_categories": [],
"top_expenses": [],
"monthly_trend": []
}π‘ The exact category, expense, and trend arrays depend on the data stored in the local database.
Use the application through an interactive shell instead of launching a new command for every action.
py -m expense_tracker.cli shellExample:
Expense Tracker Shell
expense> list
expense> add
expense> summary
expense> wallets list
expense> currency show
expense> exit
Create portable backups and restore tracker data using JSON.
py -m expense_tracker.cli export-json -o backup.json
py -m expense_tracker.cli import-json backup.jsonJSON is useful for backups, migrations, testing, and moving data between installations.
| Layer | Tool |
|---|---|
| Language | Python 3.10+ (tested on 3.14) |
| CLI framework | Click 8.x |
| Terminal UI | Rich 13.x |
| Web framework | Flask 3.x π |
| Frontend | Vanilla JS + Chart.js 4.x π |
| Design system | Custom CSS (light/dark tokens, fluid grid, no framework) |
| Database | SQLite (Python stdlib) |
| Charts (CLI) | Matplotlib 3.x |
| Testing | pytest 7.x |
| Packaging | pyproject.toml (PEP 621, modern standard) |
π‘ Zero runtime dependencies outside the standard library except for Click, Rich, Matplotlib, and Flask β all installable with one command.
expense-tracker/
β
βββ pyproject.toml # Project metadata & dependencies (PEP 621)
βββ requirements.txt # Pip-installable dependencies
βββ README.md # You are here
βββ CHANGELOG.md # Release history
βββ .gitignore # Ignore __pycache__, *.db, etc.
β
βββ src/
β βββ expense_tracker/
β βββ __init__.py # Package marker & version
β βββ __main__.py # Enables: python -m expense_tracker
β βββ cli.py # All Click commands
β βββ database.py # SQLite setup, schema, connection
β βββ models.py # Dataclasses + repository classes
β βββ reports.py # Monthly aggregation logic
β βββ visualization.py # Matplotlib charts
β βββ web.py # π Flask app + JSON REST API
β β
β βββ templates/ # π
β β βββ index.html # Single-page app shell
β β
β βββ static/ # π
β βββ css/style.css # Design system
β βββ js/app.js # SPA logic (routing, CRUD, charts)
β
βββ tests/
βββ conftest.py # Shared pytest fixtures (in-memory DB)
βββ test_models.py # Repository unit tests
βββ test_reports.py # Report generation tests
The src/ layout is the modern Python best practice β it prevents accidental imports from the working directory and forces proper packaging.
Expense Tracker is designed to run locally with a standard Python installation. You do not need MySQL, PostgreSQL, Node.js, Docker, or a cloud account for the normal setup.
The recommended setup uses a Python virtual environment so the project's dependencies stay isolated from the rest of your system.
Before installing, make sure you have:
- Python 3.10 or newer
- pip β normally bundled with Python
- Git β required when cloning the repository
- A terminal:
- PowerShell / Windows Terminal on Windows
- Terminal on macOS / Linux
- A modern browser if you plan to use the Flask dashboard
Check your installed versions before continuing:
py --version
py -m pip --version
git --versionpython3 --version
python3 -m pip --version
git --versionπ‘ Python version: Python 3.10+ is the supported baseline. The project has also been tested with newer Python versions, including Python 3.14.
Open PowerShell and choose the directory where you keep your projects:
git clone https://github.com/ItsWanheda/expense-tracker.git
cd expense-trackerIf the repository is already cloned:
cd expense-trackerCreate an isolated environment named .venv:
py -m venv .venvThis creates a local Python environment inside the project directory. You only need to create it once unless you intentionally remove it.
.\.venv\Scripts\Activate.ps1After activation, your prompt should look similar to:
(.venv) PS expense-tracker>
If PowerShell reports that script execution is disabled, run this once for your user account:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUserThen activate again:
.\.venv\Scripts\Activate.ps1python --version
python -m pip --versionUsing python -m pip after activation makes it clear that pip belongs to the active virtual environment.
python -m pip install --upgrade pip setuptools wheelKeeping pip and the build tools current can prevent installation problems when Python packages need to build wheels.
For normal runtime use:
python -m pip install -e .The -e flag installs the local project in editable mode, so source-code changes are immediately available without reinstalling the package.
For development and testing:
python -m pip install -e ".[dev]"The development extra adds the project's test tooling.
If you prefer installing the dependency list directly:
python -m pip install -r requirements.txtThis is useful for environments where you do not want to install the package itself in editable mode.
python -m expense_tracker --help
python -m expense_tracker --versionYou can also verify the installed command entry point:
expense --helpThe expected package version on the v0.5.0 development line is:
0.5.0
The database is initialized automatically when the application needs it. A safe first command is:
python -m expense_tracker categories listThis initializes the local database and shows the available categories.
python -m expense_tracker.webThen open:
http://127.0.0.1:5000
Keep the terminal window running while you use the dashboard. Press Ctrl+C to stop the development server.
git clone https://github.com/ItsWanheda/expense-tracker.git
cd expense-trackerpython3 -m venv .venvsource .venv/bin/activateYour shell should now show .venv in the prompt.
python3 -m pip install --upgrade pip setuptools wheelNormal installation:
python3 -m pip install -e .Development installation:
python3 -m pip install -e ".[dev]"Or install the dependency file directly:
python3 -m pip install -r requirements.txtpython3 -m expense_tracker --help
python3 -m expense_tracker --version
expense --helppython3 -m expense_tracker categories listpython3 -m expense_tracker.webOpen:
http://127.0.0.1:5000
Press Ctrl+C in the terminal to stop the development server.
After installation, run this small verification sequence.
# Check Python
py --version
# Check the package
py -m expense_tracker --version
# Check CLI help
py -m expense_tracker --help
# Initialize / inspect the database
py -m expense_tracker categories list
# Run the test suite
py -m pytest -vpython3 --version
python3 -m expense_tracker --version
python3 -m expense_tracker --help
python3 -m expense_tracker categories list
python3 -m pytest -vIf all commands complete successfully, the application, database layer, CLI, and test environment are ready.
Expense Tracker does not require a separate database server. It uses SQLite and automatically creates the application database when needed.
| Operating System | Database Path |
|---|---|
| Windows | C:\Users\<you>\.expense_tracker\expenses.db |
| macOS / Linux | ~/.expense_tracker/expenses.db |
The application creates the directory if necessary and initializes the schema.
On first initialization, the application seeds the standard categories:
Food π Β· Transport π Β· Housing π Β· Entertainment π¬ Β· Health π Β· Shopping ποΈ Β· Other π¦
You can create your own categories later.
Your SQLite database contains your real expense records, so back it up before manually modifying or deleting database files.
The recommended application-level backup is JSON:
py -m expense_tracker export-json -o backup.jsonRestore it with:
py -m expense_tracker import-json backup.jsonJSON backups are portable and can be used to move data between installations.
β οΈ Important: Do not delete~/.expense_tracker/expenses.dbor the Windows equivalent simply to fix an installation problem. Back up your data first.
If you plan to modify the source code, run tests, or work on new features, install the development dependencies:
py -m pip install -e ".[dev]"Then verify the development environment:
py -m pytest -vThe project uses the src/ layout, so installing the package in editable mode is the recommended development workflow.
When updating an existing checkout, back up your data first:
py -m expense_tracker export-json -o expense-backup.jsonThen update the repository:
git pullReinstall the editable package so dependency changes are picked up:
py -m pip install -e .For development:
py -m pip install -e ".[dev]"Finally verify:
py -m expense_tracker --version
py -m pytest -vDo not remove the existing database as part of a normal upgrade. The application is designed to initialize its schema without destroying existing data.
When you finish working:
deactivateThe same command works on macOS/Linux:
deactivateYou do not need to recreate .venv the next time you work on the project. Simply activate it again:
.\.venv\Scripts\Activate.ps1or:
source .venv/bin/activateA virtual environment is strongly recommended, but it is possible to install the project directly into the current Python environment:
py -m pip install -e .This approach is convenient for a disposable machine or isolated Python installation, but it can cause dependency conflicts with other Python projects.
For development machines, prefer .venv.
- No Node.js build step is required for the bundled web UI.
- No external SQLite server is required.
- No cloud account is required for normal operation.
- Currency-rate resolution can contact a remote rate source when a requested exchange rate is not already cached locally.
- The Flask
app.run()server is intended for local development, not direct public exposure. - If installation fails while downloading/building dependencies, check your Python version, pip version, network connection, and configured package index/mirror.
- If
pipis not recognized on Windows, usepy -m pipinstead.
# 1. See what categories exist (7 are auto-seeded)
py -m expense_tracker categories list
# 2. Add a few expenses
py -m expense_tracker add -a 12.50 -d "Lunch at cafe" -c Food
py -m expense_tracker add -a 45.00 -d "Uber to airport" -c Transport
py -m expense_tracker add -a 120.00 -d "Weekly groceries" -c Food
py -m expense_tracker add -a 9.99 -d "Netflix" -c Entertainment
py -m expense_tracker add -a 2500.00 -d "Rent" -c Housing
# 3. View your expenses
py -m expense_tracker list
# 4. Set a budget and see your summary
py -m expense_tracker budget 3000
py -m expense_tracker summary
# 5. Generate a chart
py -m expense_tracker chart -o my-spending.png
# 6. Export for spreadsheet
py -m expense_tracker export -o expenses.csv| Command | Description |
|---|---|
add |
Add a new expense |
list |
List expenses (with filters) |
edit ID |
Edit an existing expense |
delete ID |
Delete an expense by ID |
summary [-m MONTH] |
Show monthly summary + budget status |
budget AMOUNT [-m MONTH] [-c CATEGORY] |
Set a monthly budget (overall or per-category) π |
chart [-o FILE] |
Generate a PNG bar chart |
export [-o FILE] |
Export to CSV |
categories list |
List all categories |
categories add NAME |
Create a new category |
categories delete ID |
Delete a category |
# Minimal β defaults to today's date, no category
py -m expense_tracker add -a 25.00 -d "Book"
# Full
py -m expense_tracker add -a 45.00 -d "Uber" -c Transport --date 2024-05-15
# Create a brand-new category on the fly (it will ask)
py -m expense_tracker add -a 9.99 -d "Netflix" -c Subscriptions
# ? Category 'Subscriptions' doesn't exist. Create it? [y/N]: y# Most recent 20
py -m expense_tracker list
# Filter by date range
py -m expense_tracker list --from 2024-05-01 --to 2024-05-31
# Filter by category
py -m expense_tracker list -c Food
# Combine filters and show more
py -m expense_tracker list -c Food --from 2024-05-01 --to 2024-05-31 -n 50# Only the fields you pass get updated (others stay the same)
py -m expense_tracker edit 3 -a 130.00 -d "Weekly groceries (updated)"
py -m expense_tracker edit 3 -c Transport # change category only
py -m expense_tracker edit 3 --date 2024-05-20 # change date only
# Delete by ID
py -m expense_tracker delete 5# Current month
py -m expense_tracker summary
# Specific month
py -m expense_tracker summary -m 2024-05Output:
ββββββββββββββββββββ Summary β 2024-05 ββββββββββββββββββββ
Total: 2687.49 across 4 expenses
Budget: 3000.00 Remaining: 312.51
By Category
ββββββββββββββββ¬ββββββββ¬ββββββββββ¬ββββββ
β Category β Count β Total β % β
ββββββββββββββββΌββββββββΌββββββββββΌββββββ€
β Housing β 1 β 2500.00 β 93% β
β Food β 2 β 132.50 β 5% β
β Transport β 1 β 45.00 β 2% β
β Entertainmentβ 1 β 9.99 β 0% β
ββββββββββββββββ΄ββββββββ΄ββββββββββ΄ββββββ
If you've exceeded your budget, Remaining will turn red automatically.
# Overall monthly budget
py -m expense_tracker budget 3000
py -m expense_tracker budget 3000 -m 2024-05
# Per-category budget π
py -m expense_tracker budget 300 -c Food
py -m expense_tracker budget 50 -c Entertainment -m 2024-05You can mix both β an overall budget caps total spending, while per-category budgets cap individual categories. They're evaluated independently.
py -m expense_tracker chart -o may.png # saves may.png
py -m expense_tracker chart -o may.png -m 2024-05 # specific monthpy -m expense_tracker export -o expenses.csv
py -m expense_tracker export -o may.csv --from 2024-05-01 --to 2024-05-31The CSV has columns: id, date, category, amount, description.
# List wallets
py -m expense_tracker wallets list
# Create a wallet
py -m expense_tracker wallets add "Travel" --currency EUR# Show supported currencies
py -m expense_tracker currency show
# Convert between currencies
py -m expense_tracker currency convert 100 USD EUR
# Set a manual rate
py -m expense_tracker currency rate EUR USD 1.17Exchange rates are cached locally. When a requested pair is not available locally, the application can resolve it live and cache the result.
py -m expense_tracker shellThe REPL lets you run tracker commands interactively without restarting the CLI for every operation.
# Export a portable backup
py -m expense_tracker export-json -o backup.json
# Restore from a backup
py -m expense_tracker import-json backup.jsonAvailable in the current release. A complete single-page application that talks to the same SQLite database as the CLI β every entry you add in the browser shows up in the terminal and vice-versa.
py -m expense_tracker.webThen open http://127.0.0.1:5000 in your browser.
You can also use the Flask CLI:
export FLASK_APP=expense_tracker.web # macOS / Linux
$env:FLASK_APP = "expense_tracker.web" # Windows PowerShell
flask run --debug| Page | What it does |
|---|---|
| π Dashboard | Total spent this month, budget remaining with a progress bar, top category, and recent expenses |
| π Expenses | Full CRUD with date/category filters, modal forms for add/edit, inline delete confirmation |
| π·οΈ Categories | Grid of colored category cards with add/delete and a color picker |
| π Reports | Interactive bar + doughnut charts (Chart.js) plus a category breakdown table with Budget & Remaining columns when applicable |
| π― Budget | Manage overall and per-category budgets in one place, with per-category progress bars and an active-budgets table with Edit/Delete |
| π Wallets | Create, edit, delete, and manage multiple wallets |
| π± Currencies | View rates, enter manual rates, and convert between currencies |
| π Recurring | Manage recurring expenses and generate due entries |
The web UI talks to a small JSON REST API. You can use it directly too:
| Method | Endpoint | Purpose |
|---|---|---|
GET |
/api/health |
Health check |
GET |
/api/categories |
List categories |
POST |
/api/categories |
Create category |
DELETE |
/api/categories/<id> |
Delete category |
GET |
/api/expenses |
List expenses (filters: from, to, category_id, limit) |
POST |
/api/expenses |
Create expense |
PUT |
/api/expenses/<id> |
Update expense (partial) |
DELETE |
/api/expenses/<id> |
Delete expense |
GET |
/api/reports/summary?month=YYYY-MM |
Monthly report (incl. category_budgets) |
GET |
/api/reports/intelligence?month=YYYY-MM¤cy=USD |
Financial intelligence analytics |
GET |
/api/budget?month=β¦&category_id=β¦ |
Get a single budget |
GET |
/api/budgets?month=YYYY-MM |
List all budgets for a month π |
PUT |
/api/budget |
Set/update a budget (category_id optional) π |
DELETE |
/api/budget?month=β¦&category_id=β¦ |
Delete a budget π |
GET |
/api/export.csv |
Download CSV export |
GET |
/api/wallets |
List wallets |
GET |
/api/wallets/<id> |
Get a wallet |
POST |
/api/wallets |
Create a wallet |
PUT |
/api/wallets/<id> |
Update a wallet |
DELETE |
/api/wallets/<id> |
Delete a wallet |
GET |
/api/currencies |
List supported currencies |
GET |
/api/currencies/rates |
Get cached/resolved currency rates |
GET |
/api/currencies/rate |
Get one currency pair rate |
POST |
/api/currencies/rates |
Save a manual exchange rate |
GET |
/api/currencies/convert |
Convert an amount between currencies |
Example with curl:
# Add an expense via the API
curl -X POST http://127.0.0.1:5000/api/expenses \
-H "Content-Type: application/json" \
-d '{"amount": 12.50, "description": "Lunch", "category_id": 1, "date": "2026-06-30"}'
# Get this month's summary as JSON
curl http://127.0.0.1:5000/api/reports/summary
# Set a per-category budget
curl -X PUT http://127.0.0.1:5000/api/budget \
-H "Content-Type: application/json" \
-d '{"month": "2026-06", "amount": 300, "category_id": 1}'
# List wallets
curl http://127.0.0.1:5000/api/wallets
# Get currency rates
curl http://127.0.0.1:5000/api/currencies/rates
# Convert 100 USD to EUR
curl "http://127.0.0.1:5000/api/currencies/convert?base=USD"e=EUR&amount=100"π‘οΈ For development only. Don't expose
app.run()to the internet β usegunicorn 'expense_tracker.web:create_app()'behind a reverse proxy in production.
This project follows a layered architecture that separates concerns cleanly:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Presentation Layer β
β ββββββββββββββββββββββββ ββββββββββββββββββββββββββββ β
β β cli.py (Click+Rich) β β web.py (Flask + JS SPA) β β
β ββββββββββββ¬ββββββββββββ ββββββββββββββββ¬ββββββββββββ β
βββββββββββββββΌββββββββββββββββββββββββββββββββΌββββββββββββββββ
β β
βββββββββββββββββ¬ββββββββββββββββ
β calls
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Repository Layer (models.py) β
β - Static methods per entity (CRUD + queries) β
β - Returns dataclasses, not raw rows β
β - Shared by both the CLI and the web app β
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ
β uses
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Data Layer (database.py) β
β - SQLite connection management (context manager) β
β - Schema definition & migrations β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Cross-cutting:
reports.py β aggregation + financial intelligence (CLI + web)
visualization.py β matplotlib charts (CLI only)
wallets/currency β shared wallet and exchange-rate repositories
| Layer | Responsibility | Why it matters |
|---|---|---|
| Data | Manage the connection & schema | One place to change the database |
| Repository | Translate Python β SQL | Easy to swap SQLite for Postgres later |
| Presentation | Talk to the user (terminal or browser) | Multiple UIs share the same logic |
The web layer is just a thin Flask wrapper around the same repositories the CLI uses β zero duplicated SQL, zero duplicated business logic.
- Idempotent
initialize_database()β called on every startup (CLI + web); safe to run repeatedly - Idempotent
CategoryRepository.create()β returns existing ID if name is taken (no surprises) - Python-level upserts for
BudgetRepositoryβ avoids SQLite's NULL + UNIQUE pitfall - Timezone-aware datetimes β
datetime.now(timezone.utc), not deprecatedutcnow() - Context-managed DB connections β auto-commit on success, auto-rollback on error
- Single DB, two UIs β CLI and web read/write the same
~/.expense_tracker/expenses.db
CREATE TABLE categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
color TEXT DEFAULT '#3498db',
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE expenses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
amount REAL NOT NULL CHECK (amount > 0),
description TEXT NOT NULL,
category_id INTEGER,
date TEXT NOT NULL, -- ISO: YYYY-MM-DD
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL
);
CREATE TABLE budgets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category_id INTEGER, -- NULL = overall budget
month TEXT NOT NULL, -- YYYY-MM
amount REAL NOT NULL CHECK (amount >= 0),
UNIQUE (category_id, month),
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE
);Food π Β· Transport π Β· Housing π Β· Entertainment π¬ Β· Health π Β· Shopping ποΈ Β· Other π¦
| OS | Path |
|---|---|
| Windows | C:\Users\<you>\.expense_tracker\expenses.db |
| macOS / Linux | ~/.expense_tracker/expenses.db |
# Run all tests, verbose
py -m pytest -v
# Run with coverage report
py -m pytest --cov=expense_tracker --cov-report=term-missing
# Run a single file
py -m pytest tests/test_models.py -v
# Run a single test
py -m pytest tests/test_models.py::test_update_expense -vtests/test_models.py::test_add_and_get_expense PASSED
tests/test_models.py::test_update_expense PASSED
tests/test_models.py::test_delete_expense PASSED
tests/test_models.py::test_list_with_filters PASSED
tests/test_models.py::test_budget_set_and_get PASSED
tests/test_reports.py::test_monthly_report PASSED
========================== 6 passed in 0.4s ==========================
The tmp_db fixture in conftest.py:
- Creates a temporary SQLite file for each test (via
tmp_path) - Patches
database.get_db_pathto point at it - Initializes the schema
- Cleans up automatically when the test ends
This means tests never touch your real database β completely safe.
β python is not recognized (Windows)
Reinstall Python from python.org and check the box:
β Add python.exe to PATH
Alternatively, use py (the Python Launcher) which is installed automatically on Windows:
py --versionβ PowerShell blocks script activation
Run once as your user:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUserThen try activating again:
.\.venv\Scripts\Activate.ps1β IndentationError after pasting code
Code copied from chat sometimes loses spaces. Verify the syntax with:
py -m compileall src\expense_tracker -qIf there's an error, open the offending file in your editor and fix the indentation manually.
β OperationalError: no such table
Both the CLI and the web app auto-initialize the database on startup. If you still see this, your ~/.expense_tracker/ directory may be locked or unreadable. Try:
# Delete and recreate
Remove-Item -Recurse -Force ~\.expense_tracker
py -m expense_tracker categories list # this re-creates everythingβ UNIQUE constraint failed: categories.name
You tried to create a category that already exists. The CLI handles this automatically by asking, but if you see it in code, use CategoryRepository.create() which is idempotent:
# Returns existing ID if "Food" exists, otherwise creates it
cat_id = CategoryRepository.create("Food")
β Web UI won't load (404 on /static/β¦)
Make sure you launched via the package, not a stray script:
# β
Correct
py -m expense_tracker.web
# β Wrong (Flask can't find templates/static)
cd src && py expense_tracker/web.py
- Open
src/expense_tracker/cli.py
- Add a new function decorated with
@cli.command() (or @<group>.command())
- Implement it using existing repositories β don't add SQL here
- (Optional) Add a matching API endpoint in
web.py
- (Optional) Add a UI section in
static/js/app.js
- Add a test in
tests/
- Add the column to
SCHEMA in database.py
- Add a migration note to handle existing databases
- Update the relevant dataclass in
models.py
- Update repository methods that touch that field
- Update
reports.py / web.py / app.js if the field is exposed to users
- Add tests for the new behavior
- PEP 8 for naming and layout
- Type hints on all public functions
- Docstrings for all public classes and functions
- Dataclasses for value objects, not plain dicts
- No raw SQL in CLI or web code β always go through a repository
The current v0.5.0 development line includes:
- π Multiple wallets
- π± Multi-currency expenses and wallets
- π Cached and live currency-rate resolution
- π± Currency conversion
- π Interactive REPL mode
- π¦ JSON import/export
- π Recurring expenses
- π Advanced charts
- π Flask web dashboard and REST API
- π― Overall and per-category budgets
Planned for future releases:
- Core CRUD for expenses and categories β
- Monthly summary & overall budget tracking β
- CSV export β
- Matplotlib charts (CLI) β
- Pytest test suite β
- Per-category budgets with progress bars β
(
0.2.0)
- Web interface using the same repositories β
(
0.2.0)
- Interactive charts (Chart.js in the web UI) β
(
0.2.0)
- Responsive design β sidebar drawer + stacked layouts + scrollable tables β
(
0.3.0)
- Light/dark theme with live chart recoloring β
(
0.3.0)
- Command palette & keyboard shortcuts β
(
0.3.0)
- Toast with Undo action for accidental deletes β
(
0.3.0)
- Recurring expenses (rent, subscriptions) β
(
0.4.0)
- Advanced Charts β
(
0.4.0)
- Multi-currency support with cached/live conversion rates β
(
0.4.0)
- Interactive REPL mode (
expense shell) β
(0.4.0)
- JSON import / export β
(
0.4.0)
- Multiple Wallets with wallet-level currencies and expense assignment β
(
0.4.0)
- Financial Intelligence with trends, comparisons, daily metrics, and top-spending analysis π§ (
0.5.0)
- Telegram / Discord bot integration
- GitHub Actions CI (run tests on every push)
- Publish to PyPI (
pip install expense-tracker)
- Tag system (many-to-many)
- Pre-commit hooks (black, ruff, mypy)
- User Authentication
- Cloud Sync
- Notifications
- AI Spending Insights
- Progressive Web App (PWA)
- Docker Support
- PDF Report Export
- Advanced Analytics
- Localization & Multi-language
- User Accounts & Profiles
- Excel Export
- PostgreSQL/MySQL Support
Contributions of all sizes are welcome! Here's the workflow:
- Fork the repository
- Create a branch for your feature:
git checkout -b feature/per-category-budgets
- Make your changes and add tests
- Run the test suite to make sure nothing broke:
py -m pytest -v
- Commit with a clear message:
git commit -m "Add per-category budgets with progress bars"
- Push and open a Pull Request
Please open an issue first if you want to discuss a big change before implementing it.
This project is licensed under the MIT License β see the LICENSE file for details. You're free to use, modify, and distribute it, commercially or otherwise.
Q: Is my financial data safe?
A: 100%. Everything is stored in a single SQLite file on your machine. There is no cloud sync, no telemetry, no analytics β nothing leaves your computer.
Q: Can I sync between machines?
A: Yes β just copy ~/.expense_tracker/expenses.db between devices. You could put it in Dropbox/Syncthing/etc. for automatic syncing.
Q: Can I import data from my bank?
A: Not yet, but a CSV import command is on the roadmap. In the meantime, you can bulk-insert via a small Python script using the existing repositories.
Q: Why Click instead of argparse?
A: Click gives us nested subcommands (categories add), automatic --help for every level, and better ergonomics with about 60% less code than argparse.
Q: Can I extend this with a web UI?
A: You don't have to β there's already one in 0.2.0! py -m expense_tracker.web starts the Flask server. If you want to add your own, the repository pattern makes it trivial: import the repos and return JSON.
Q: Can the CLI and web app be used at the same time?
A: Yes. SQLite supports concurrent reads from the same connection pool. Both UIs read/write the same expenses.db, so changes in one appear instantly in the other.
Q: Why no ORMs (SQLAlchemy, Tortoise)?
A: For a small project, raw SQL with the repository pattern is simpler, faster, and gives you full control. ORMs add abstraction layers that aren't justified at this scale.
Q: Why Flask and not FastAPI?
A: Flask's templating + static-file serving made the bundled SPA dead-simple to ship without a separate build step. The REST API uses plain JSON over HTTP, so a future migration to FastAPI is mostly mechanical if performance or async becomes important.
If this project helped you learn something or saved you time, give it a star on GitHub! It helps others discover it.
Made with β€οΈ and lots of β