Skip to content

title: Migrating from Python Beancount description: Switch from Python beancount to rustledger ​

Migrating from Python Beancount ​

rustledger is designed as a drop-in replacement for Python beancount with 10-30x better performance.

Quick Start ​

Your existing beancount files work as-is:

bash
# Validate with rustledger
rledger check ledger.beancount

# Run queries
rledger query ledger.beancount "SELECT account, sum(position) GROUP BY account"

Compatibility ​

Fully Compatible ​

  • All beancount syntax
  • All directive types (transaction, balance, open, close, etc.)
  • All booking methods (FIFO, LIFO, STRICT, etc.)
  • BQL query language
  • Include directives
  • Options
  • Metadata

Plugin Compatibility ​

PluginStatusNotes
auto_accounts✅ NativeFaster implementation
implicit_prices✅ NativeFaster implementation
check_commodity✅ Native
coherent_cost✅ Native
leafonly✅ Native
noduplicates✅ Native
onecommodity✅ Native
sellgains✅ Native
unique_prices✅ Native
Custom Python plugins⚠️ WASMRequires compilation

See Plugins Reference for full list.

Known Differences ​

  1. Decimal precision: rustledger uses 28-digit precision vs Python's arbitrary precision. This only affects extreme edge cases (28+ decimal places).

  2. Error messages: Format differs but contains same information.

  3. Plugin loading: Python plugins require WASM compilation.

  4. Repeated same-day lots (no longer a difference since v0.23.0): buying the same holding more than once on one day at the same cost and label creates one lot in both engines, as Python beancount always has. Earlier rustledger releases kept one lot per purchase. When a lot at another cost was bought between two such purchases, FIFO and LIFO then consumed the lots in a different order from Python, so cost basis and realized gains could differ, or a later reduction naming an explicit cost could fail on one engine only. If you compared figures with an older release and saw that kind of difference on a holding bought several times in one day, upgrade. Use distinct labels ({10.00 USD, "morning"}) to keep same-day purchases apart. Fixed in #2118.

  5. Adding to one side of a long-and-short holding: if an account holds a commodity both long and short, a posting whose cost names a lot of its own sign adds to that side as a new lot, dated the transaction date. Holding -2 X {101 USD} and 5 X {102 USD}, buying 3 X {102 USD} gives a separate 3 X {102 USD} lot. Python beancount's lot matching ignores sign, so it merges those units into the existing 102 lot, which gives them that lot's older acquisition date, and it rejects the same posting (Not enough lots to reduce) when the quantity is larger than that lot. Units and cost basis agree; the lot date, and whether the ledger is accepted, can differ. A cost that matches no lot on either side is rejected by both, as is a posting that names only a lot date or label. Tracked in #2384.

Migration Steps ​

1. Install rustledger ​

bash
cargo install rustledger

2. Validate Your Ledger ​

bash
rledger check ledger.beancount

Compare output with Python beancount:

bash
bean-check ledger.beancount

3. Test Reports ​

bash
# Balance report
rledger report ledger.beancount balances

# Compare with
bean-report ledger.beancount balances

4. Test Queries ​

bash
rledger query ledger.beancount "SELECT account, sum(position) GROUP BY account"

5. Update Your Workflow ​

Replace beancount commands:

Python Beancountrustledger
bean-checkrledger check
bean-queryrledger query
bean-reportrledger report
bean-formatrledger format
bean-pricerledger price
bean-extractrledger extract

Or install wrapper scripts so existing scripts work without changes:

bash
rledger compat install

6. Update Editor ​

If using VS Code or other editors with Python beancount LSP, switch to rustledger LSP for better performance.

Plugin Migration ​

Python Plugins to WASM ​

For custom Python plugins, you have options:

  1. Rewrite in Rust: Add to rustledger-plugin/src/native/
  2. Compile to WASM: Use py2wasm (experimental)
  3. Use pre/post hooks: For simple transformations

Check Plugin Equivalents ​

Many Python plugins have native Rust equivalents. When a plugin name matches a built-in, the declaration is unchanged — it resolves to the native Rust implementation:

beancount
; Before (Python) and after (rustledger) — identical.
; Resolves to the native `auto_accounts`, not Python.
plugin "beancount.plugins.auto_accounts"

Custom Python Plugins: Reference by File Path ​

beancount's module-name plugin syntax does not carry over for custom plugins (those without a native equivalent). rustledger does not search the system Python path, so a bare module name is rejected — reference the file directly instead:

beancount
; ❌ Not supported for a custom plugin
plugin "mypackage.mymodule"

; ✅ Reference the .py file (absolute, or relative to the ledger)
plugin "/abs/path/to/mymodule.py"

The plugin also runs in a sandbox that cannot see your virtualenv, so it must be self-contained (standard library plus the bundled beancount compat shim). See Referencing a Python Plugin for the full model.

Performance Comparison ​

Typical speedups on real ledgers:

Ledger SizePythonrustledgerSpeedup
1,000 txns2.5s0.1s25x
10,000 txns8s0.3s27x
50,000 txns35s1.2s29x

Troubleshooting ​

"Unknown plugin" Error ​

The plugin may not be implemented yet. Check Plugins Reference or open an issue.

Different Balance ​

Check for precision differences:

bash
# Python
bean-query ledger.beancount "SELECT sum(position) WHERE account ~ 'Assets'"

# rustledger
rledger query ledger.beancount "SELECT sum(position) WHERE account ~ 'Assets'"

If amounts differ by tiny fractions (e.g., 1e-20), it's a precision difference and can be ignored.

Query Syntax Differences ​

BQL is compatible, but check:

  • Date literals: Use 2024-01-15 not "2024-01-15"
  • Regex: Use account ~ "pattern" for regex matching

See Also ​