HomeBudget comprehensive guide¶
Table of contents¶
- Getting started
- Installation
- Configuration
- Basic usage
- Entry points
- Forex input rules
- Working with expenses
- Working with income
- Working with transfers
- Batch operations
- Troubleshooting
Getting started¶
The HomeBudget wrapper is a Python library and CLI that lets you manage HomeBudget data through a SQLite database. The wrapper supports expense, income, and transfer operations with full CRUD (create, read, update, delete) functionality. It also supports sync updates so changes appear in the HomeBudget apps across all devices.
What you need
- Python 3.10 or later
- SQLite 3.35 or later
- A HomeBudget database file
Installation¶
Using Git Bash on Windows for all examples.
Download the latest wheel distribution from the releases page and install it. Replace the version number as needed.
RELEASE_VERSION=$(cat VERSION)
WHEEL_FILE=homebudget-$RELEASE_VERSION-py3-none-any.whl
DOWNLOAD_URL=https://github.com/yayfalafels/homebudget/releases/download/v$RELEASE_VERSION/$WHEEL_FILE
curl -L -o $WHEEL_FILE $DOWNLOAD_URL
Create and install a python virtual environment at env/
Install the package from the wheel.
Configuration¶
The wrapper requires a configuration file for database path and settings. See the Configuration Guide for complete setup instructions.
Quick setup:
# Create config directory and copy sample
config_dir="$USERPROFILE/OneDrive/Documents/HomeBudgetData"
mkdir -p "$config_dir"
cp config/hb-config.json.sample "$config_dir/hb-config.json"
# Edit the config file to set db_path
Configuration file location:
For full configuration options and troubleshooting, see Configuration Guide.
Basic usage¶
For a complete set of examples, see the methods and CLI guides.
Entry points¶
Library entry point
- Use the
HomeBudgetClientclass to perform CRUD operations - Use DTO classes to validate data before writing to the database
CLI entry point
- Use
homebudgetorhbfor the command line interface - Provide
--dbfor the database path whenhb-config.jsonis not configured
Sync behavior¶
Sync updates are enabled by default and mandatory for all write commands. This ensures consistency between local and remote devices:
- Creates SyncUpdate records in the database
- Enables mobile and other HomeBudget clients to synchronize changes
- Automatically manages the HomeBudget UI to maintain data consistency (Feature 001)
Sync cannot be disabled via the CLI to prevent irreparable breaks between local and remote data.
Forex input rules¶
Forex inputs use one of two paths for expenses and income.
Base currency path
- Provide amount only
- Currency defaults to the account currency
- Currency amount is set to amount for base currency updates
- Exchange rate is treated as 1.0
Foreign currency path
- Provide currency and currency amount
- Exchange rate is optional (if omitted, the wrapper infers a rate from the forex cache)
- Amount is calculated as currency amount times exchange rate
You can pass an explicit rate with --exchange-rate (CLI) or exchange_rate (client updates).
Do not provide amount and currency amount together.
Currency matching constraints¶
When adding or updating transactions (expense, income, transfer), the currency you specify must match the account's base currency rules:
For expenses and income:
- Cannot add base currency transactions to non-base currency accounts: If an account's currency is non-base (e.g., USD account "TWH IB USD"), you cannot add a transaction in the system's base currency (SGD). The account will reject base currency inputs.
- Can add foreign currency transactions to base currency accounts: If an account's currency is the system base (SGD), you can add transactions in foreign currencies (USD, EUR, GEL, etc.) by specifying
--currency,--currency-amount, and optionally--exchange-rate.
Examples:
- ✓ VALID: Add USD expense to "TWH - Personal" (SGD base) with
--currency USD --currency-amount 27.50 --exchange-rate 0.9273 - ✗ INVALID: Add SGD income to "TWH IB USD" (USD base) - account does not accept base currency
- ✓ VALID: Add USD income to "DBS Multi" (SGD base) with
--currency USD --currency-amount 2700 --exchange-rate 0.7407
For transfers between mixed-currency accounts:
- You can specify the currency amount and exchange rate for either the source or destination account's currency, but not both
- Examples:
- ✓ VALID: Transfer from "TWH - Personal" (SGD) to "TWH IB USD" (USD) specifying USD:
--currency USD --currency-amount 110 --exchange-rate 0.9091 - ✓ VALID: Same transfer specifying SGD:
--currency SGD --currency-amount 121 --exchange-rate 0.826 - ✗ INVALID: Mix both currencies in a single transfer - choose one accounts currency
- ✗ INVALID: Specify a third currency (e.g., EUR) that does not match either account's currency
UI Control (Automatic)¶
When you run a write command with sync enabled (the default), the HomeBudget UI is automatically closed during the database operation and automatically reopened when complete.
Why this happens:
- Ensures the UI doesn't read incomplete data during database changes
- Prevents database locks that occur while the UI reads during sync operations
- Atomic database transactions are guaranteed without UI interference
What you'll observe:
- The HomeBudget application window will briefly close and reopen
- The entire operation (close → change → reopen) typically takes 6-11 seconds
- No manual intervention is needed; it's completely automated
Feature mapping¶
| HomeBudget UI operation | Wrapper method | CLI command |
|---|---|---|
| Add expense | HomeBudgetClient.add_expense | homebudget expense add |
| List expenses | HomeBudgetClient.list_expenses | homebudget expense list |
| Get expense | HomeBudgetClient.get_expense | homebudget expense get |
| Update expense | HomeBudgetClient.update_expense | homebudget expense update |
| Delete expense | HomeBudgetClient.delete_expense | homebudget expense delete |
| Add income | HomeBudgetClient.add_income | homebudget income add |
| List income | HomeBudgetClient.list_incomes | homebudget income list |
| Get income | HomeBudgetClient.get_income | homebudget income get |
| Update income | HomeBudgetClient.update_income | homebudget income update |
| Delete income | HomeBudgetClient.delete_income | homebudget income delete |
| Add transfer | HomeBudgetClient.add_transfer | homebudget transfer add |
| List transfers | HomeBudgetClient.list_transfers | homebudget transfer list |
| Get transfer | HomeBudgetClient.get_transfer | homebudget transfer get |
| Update transfer | HomeBudgetClient.update_transfer | homebudget transfer update |
| Delete transfer | HomeBudgetClient.delete_transfer | homebudget transfer delete |
| Batch add expenses | HomeBudgetClient.add_expenses_batch | homebudget expense batch-import |
| Batch add income | HomeBudgetClient.add_incomes_batch | homebudget income batch-import |
| Batch add transfers | HomeBudgetClient.add_transfers_batch | homebudget transfer batch-import |
| Start UI | N/A | homebudget ui start |
| Close UI | N/A | homebudget ui close |
| Refresh UI | N/A | homebudget ui refresh |
| Check UI status | N/A | homebudget ui status |
Working with expenses¶
Use the expense methods to add, list, update, and delete transactions. Sync updates are created for write operations when sync is enabled.
Add an expense.
from decimal import Decimal
import datetime
from homebudget import HomeBudgetClient, ExpenseDTO
with HomeBudgetClient() as client:
expense = ExpenseDTO(
date=datetime.date(2026, 2, 16),
category="Dining",
subcategory="Restaurant",
amount=Decimal("25.50"),
account="Wallet",
notes="Lunch"
)
saved = client.add_expense(expense)
print(saved.key)
List expenses for a date range.
import datetime
from homebudget import HomeBudgetClient
with HomeBudgetClient() as client:
results = client.list_expenses(
start_date=datetime.date(2026, 2, 1),
end_date=datetime.date(2026, 2, 28)
)
for expense in results:
print(expense.key, expense.amount)
Add an expense with the CLI.
homebudget expense add \
--date 2026-02-16 \
--category Dining \
--subcategory Restaurant \
--amount 25.50 \
--account Wallet \
--notes "Lunch"
Add an expense with a foreign currency and exchange rate.
homebudget expense add \
--date 2026-02-16 \
--category Dining \
--subcategory Restaurant \
--currency EUR \
--currency-amount 12.00 \
--exchange-rate 1.10 \
--account Wallet \
--notes "Lunch"
Working with income¶
Use the income methods to add, list, update, and delete income transactions. Sync updates are created for write operations when sync is enabled.
Add income.
from decimal import Decimal
import datetime
from homebudget import HomeBudgetClient, IncomeDTO
with HomeBudgetClient() as client:
income = IncomeDTO(
date=datetime.date(2026, 2, 28),
name="Salary",
amount=Decimal("5000.00"),
account="Bank",
notes="February salary"
)
saved = client.add_income(income)
print(saved.key)
List income for a date range.
import datetime
from homebudget import HomeBudgetClient
with HomeBudgetClient() as client:
results = client.list_incomes(
start_date=datetime.date(2026, 2, 1),
end_date=datetime.date(2026, 2, 28)
)
for income in results:
print(income.key, income.amount)
Add income with the CLI.
homebudget income add \
--date 2026-02-28 \
--name "Salary" \
--amount 5000.00 \
--account Bank \
--notes "February salary"
Working with transfers¶
Use the transfer methods to add, list, update, and delete transfers between accounts. Transfers support a currency normalization layer that allows flexible input for mixed-currency transfers.
Currency Normalization Layer¶
When transferring between accounts with different currencies, you can specify the amount in three ways:
- Amount Only (Inference): Provide only
--amount. The system infers the currency:
If base currency in either account → amount is in base currency
If base in neither account → amount is in from_account currency
- From-Currency Explicit: Provide
--currency+--currency-amountmatching the from_account
Already in backend format → passes through unchanged
- To-Currency Explicit: Provide
--currency+--currency-amountmatching the to_account
System normalizes to backend format using inverse forex calculation
For detailed normalization rules, see docs/transfer-currency-normalization.md.
Basic Transfer Examples¶
Add a same-currency transfer.
from decimal import Decimal
import datetime
from homebudget import HomeBudgetClient, TransferDTO
with HomeBudgetClient() as client:
transfer = TransferDTO(
date=datetime.date(2026, 2, 20),
from_account="Bank",
to_account="Wallet",
amount=Decimal("200.00"),
notes="Cash withdrawal"
)
saved = client.add_transfer(transfer)
print(saved.key)
Add a mixed-currency transfer (amount only).
# Transfer from SGD account to USD account
# System infers: amount is in base currency (SGD)
with HomeBudgetClient() as client:
transfer = TransferDTO(
date=datetime.date(2026, 2, 20),
from_account="TWH - Personal", # SGD base
to_account="TWH IB USD", # USD
amount=Decimal("200.00"), # Interpreted as SGD
notes="Transfer to USD account"
)
saved = client.add_transfer(transfer)
print(saved.key)
Add a mixed-currency transfer (explicit from-currency).
# Specify amount in from_account currency (USD)
with HomeBudgetClient() as client:
transfer = TransferDTO(
date=datetime.date(2026, 2, 20),
from_account="TWH IB USD", # USD
to_account="TWH - Personal", # SGD base
amount=None, # Will be calculated
currency="USD", # Matches from_account
currency_amount=Decimal("150.00"), # Amount in USD
notes="Transfer to SGD account"
)
saved = client.add_transfer(transfer)
print(saved.key)
Add a mixed-currency transfer (explicit to-currency, normalized).
# Specify amount in to_account currency (EUR)
# System normalizes to backend format (from_account currency)
with HomeBudgetClient() as client:
transfer = TransferDTO(
date=datetime.date(2026, 2, 20),
from_account="TWH IB USD", # USD
to_account="Cash TWH EUR", # EUR
amount=None, # Will be calculated
currency="EUR", # Matches to_account (will be normalized)
currency_amount=Decimal("90.00"), # Amount in EUR
notes="Transfer to EUR account"
)
saved = client.add_transfer(transfer)
print(saved.key)
List transfers for a date range.
import datetime
from homebudget import HomeBudgetClient
with HomeBudgetClient() as client:
results = client.list_transfers(
start_date=datetime.date(2026, 2, 1),
end_date=datetime.date(2026, 2, 28)
)
for transfer in results:
print(transfer.key, transfer.amount)
CLI Transfer Examples¶
Add a same-currency transfer.
homebudget transfer add \
--date 2026-02-20 \
--from-account "Bank" \
--to-account "Wallet" \
--amount 200.00 \
--notes "Cash withdrawal"
Add a mixed-currency transfer (amount only).
# Transfer from SGD to USD - amount inferred as base currency (SGD)
homebudget transfer add \
--date 2026-02-20 \
--from-account "TWH - Personal" \
--to-account "TWH IB USD" \
--amount 200.00 \
--notes "Transfer to USD account"
Add a mixed-currency transfer (explicit from-currency).
# Specify amount in from_account currency (USD)
homebudget transfer add \
--date 2026-02-20 \
--from-account "TWH IB USD" \
--to-account "TWH - Personal" \
--currency USD \
--currency-amount 150.00 \
--exchange-rate 1.35 \
--notes "Transfer to SGD account"
Add a mixed-currency transfer (explicit to-currency, normalized).
# Specify amount in to_account currency (EUR) - system normalizes
homebudget transfer add \
--date 2026-02-20 \
--from-account "TWH IB USD" \
--to-account "Cash TWH EUR" \
--currency EUR \
--currency-amount 90.00 \
--exchange-rate 0.92 \
--notes "Transfer to EUR account"
Batch operations¶
Batch operations support two paths. Use batch import for single resource files, or use the mixed operation JSON format to combine resources and operations in one run.
Batch behavior¶
- Sync optimization: Individual transactions are added without sync, then a single sync operation occurs after the batch completes
- Error handling: By default, batch continues on error and reports failures at the end. Use
--stop-on-errorto halt on first failure - Transaction atomicity: Each individual transaction insert is atomic, but the batch as a whole may partially succeed
- Validation: Each record is validated before insertion same as single-record operations
CSV format¶
CSV files must include a header row with these columns:
Expense CSV columns:
daterequired: YYYY-MM-DD formatcategoryrequired: Category namesubcategoryoptional: Subcategory nameamountrequired: Decimal amountaccountrequired: Account namenotesoptional: Transaction notescurrencyoptional: Currency codecurrency_amountoptional: Foreign currency amountexchange_rateoptional: Exchange rate to base currency
Income CSV columns:
daterequired: YYYY-MM-DD formatnamerequired: Income nameamountrequired: Decimal amountaccountrequired: Account namenotesoptional: Transaction notescurrencyoptional: Currency codecurrency_amountoptional: Foreign currency amountexchange_rateoptional: Exchange rate to base currency
Transfer CSV columns:
daterequired: YYYY-MM-DD formatfrom_accountrequired: Source account nameto_accountrequired: Destination account nameamountoptional: Destination amount Decimalnotesoptional: Transaction notescurrencyoptional: Source Currency codecurrency_amountoptional: Source currency amountexchange_rateoptional: Exchange rate from currency -> to currency
CSV example for expenses.csv:
date,category,subcategory,amount,account,notes
2026-02-01,Food (Basic),Groceries,45.50,Wallet,Weekly groceries
2026-02-03,Transport,Fuel,60.00,Credit Card,Gas station
2026-02-05,Food (Basic),Cheap restaurant,15.75,Wallet,Lunch
JSON format¶
JSON files must contain an array of transaction objects with the same fields as CSV columns.
JSON example for income.json:
[
{
"date": "2026-02-01",
"name": "Salary",
"amount": "5000.00",
"account": "Checking",
"notes": "Monthly salary"
},
{
"date": "2026-02-15",
"name": "Freelance",
"amount": "1200.00",
"account": "Checking",
"notes": "Project payment"
}
]
Transfer batch with currency normalization - transfers_batch.json:
[
{
"date": "2026-02-22",
"from_account": "TWH - Personal",
"to_account": "TWH IB USD",
"amount": "200.00",
"notes": "SGD->USD Amount Only (inferred)"
},
{
"date": "2026-02-22",
"from_account": "TWH - Personal",
"to_account": "TWH IB USD",
"currency": "SGD",
"currency_amount": "250.00",
"notes": "Explicit From Currency (SGD)"
},
{
"date": "2026-02-22",
"from_account": "TWH - Personal",
"to_account": "TWH IB USD",
"currency": "USD",
"currency_amount": "100.00",
"notes": "Explicit To Currency (USD) - normalized to backend format"
}
]
Note: Transfer batch imports support the currency normalization layer. You can specify currency + currency_amount for either the from_account or to_account. The system automatically normalizes to backend format (currency = from_account) during import.
Mixed operation JSON format¶
Use this format to batch a mix of resources and CRUD operations in one file.
Each entry is a JSON object with these keys.
resourcewith values expense, income, or transferoperationwith values add, update, or deleteparameterswith the same fields used by the single record CLI commands
Example JSON for operations.json:
[
{
"resource": "expense",
"operation": "add",
"parameters": {
"date": "2026-02-16",
"category": "Dining",
"subcategory": "Restaurant",
"amount": "25.50",
"account": "Wallet",
"notes": "Lunch"
}
},
{
"resource": "income",
"operation": "update",
"parameters": {
"key": 14021,
"notes": "Updated notes"
}
},
{
"resource": "transfer",
"operation": "delete",
"parameters": {
"key": 21007
}
}
]
Methods usage¶
from homebudget import HomeBudgetClient
from homebudget.models import ExpenseDTO
from decimal import Decimal
import datetime as dt
with HomeBudgetClient() as client:
expenses = [
ExpenseDTO(
date=dt.date(2026, 2, 1),
category="Food (Basic)",
subcategory="Groceries",
amount=Decimal("45.50"),
account="Wallet",
notes="Weekly groceries"
),
ExpenseDTO(
date=dt.date(2026, 2, 3),
category="Transport",
subcategory="Fuel",
amount=Decimal("60.00"),
account="Credit Card",
notes="Gas station"
)
]
result = client.add_expenses_batch(expenses)
print(f"Successful: {len(result.successful)}")
print(f"Failed: {len(result.failed)}")
for dto, error in result.failed:
print(f" Failed: {dto.date} {dto.amount} - {error}")
CLI usage¶
Import expenses from CSV:
Import income from JSON:
Import with error report:
Stop on first error:
Run mixed batch operations:
Run mixed batch with stop-on-error:
Run mixed batch with error report:
Batch result output¶
The CLI displays a summary of batch results:
Batch import completed
=====================
Total records: 25
Successful: 23
Failed: 2
Failed records:
Row 15: 2026-02-10 45.50 - DuplicateError: Matching expense already exists
Row 22: 2026-02-18 invalid - ValueError: Invalid amount format
Sync completed for 23 transactions
If --error-report is specified, failed records are written to the file with details.
Troubleshooting¶
- The setup script fails to install a dependency. Check requirements.txt and confirm the package source.
- The CLI reports a database error. Confirm the database file path is correct and that the file is not locked.
- Sync does not update the mobile app. Confirm sync is enabled and that DeviceInfo is present in the database.
- A duplicate error appears. Review existing transactions for matching date, account, amount, category, and notes.
- A command fails with a foreign currency error. Confirm the transaction currency matches the account currency.
- The Python version is too old. Install Python 3.10 or later and recreate the env directory.
- The SQLite version is too old. Use a Python build that includes SQLite 3.35 or later.