Hi! The other day I was building some script to parse some bank extracts and merge it into a GnuCash database through a Python script. GnuCash's offers some Python bindings to interact with it, but the documentation is a bit lacking.

The Python bindings can be understood looking into the sample scripts [1] [2] [3] and a bit of Python's help(). But if you want another entry point here is a bit of a sloppy quickstart to see how to open a GnuCash file, read it's transactions and add a new one.

(Note: If your file is stored in a SQL format you might be able to use piecash to access it. I can't talk about the how friendly that library is, but it's documentation is much more extensive).

(Also note: I'm not an expert user (or someone with accounting knowledge for that matter) and I won't go into how to use GnuCash generally as a software tool… maybe check it's wiki or it's Quick Start Guide For Business Users for that 🤷.)

But first…

What is GnuCash?

According to it's website

GnuCash is personal and small-business financial-accounting software, freely licensed under the GNU GPL […]. Designed to be easy to use, yet powerful and flexible, GnuCash allows you to track bank accounts, stocks, income and expenses. As quick and intuitive to use as a checkbook register, it is based on professional accounting principles to ensure balanced books and accurate reports.

Table of contents

  1. What is GnuCash
  2. Quick overview
  3. Preparation
  4. Opening and reading transactions
  5. Adding a new transaction
  6. References

Quick overview

Before we start, and in case you end up here trying to understand how to use the library, let me show you a quick overview of how to move around the objects in the library.

A diagram with the classes and methods to navigate between them

Anyway… back to the step-by-step…

Preparation

Python bindings installation

(On GnuCash's wiki)

On Debian or Ubuntu-based environments it is possible to just run sudo apt-get install gnucash python3-gnucash to pull the software and Python's bindings.

Note that if you're operating on a Python virtual environment the bindings might not be found by Python's interpreter.

$ python3
Python 3.11.5 (main, Aug 29 2023, 15:31:31) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import gnucash
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'gnucash'
>>>
WARNING: What comes now is something that has worked for a quick test on my local machine, DO NOT consider this a stable method and be sure to make a backup of your files before operating like this, as it might corrupt your files.

As a quick fix to find gnucash's bindings on your virtual environment you might want to try to copy them from your system environment:
$ cp -Rv /usr/lib/python3/dist-packages/gnucash $VIRTUAL_ENV/lib/python3.*/site-packages/
$ python
Python 3.11.5 (main, Aug 29 2023, 15:31:31) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import gnucash
>>> gnucash.__file__
'/home/kenkeiras/test/lib/python3.11/site-packages/gnucash/__init__.py'

Test file

Just so we have some common data to look at, let's pick this sample GnuCash file and use it for tests.

When opening it with GnuCash we can find the following accounts:

* Assets            (  1,00€)
  * Current Assets  (  1,00€)
    * Savings       (  1,00€)
    * Wallet        (  0,00€)

* Expenses          (  2,00€)
  * Sweets          (  2,00€)

* Income            (  3,00€)
  * Allowance       (  3,00€)

Screenshot showing 3 main accounts (Assets, Expenses, Income) with a total of 4 sub-accounts (Savings, Wallet, Sweets, Allowance)

Opening and reading transactions

As we can see on the Quick Overview, the first thing we have to do is to open a Session.

import gnucash

with gnucash.Session("sample-file.gnucash") as session:

Anyway… after we open a Session we can pull the Book and from there, pull the "root" Account.

import gnucash

with gnucash.Session("sample-file.gnucash") as session:
    book: gnucash.gnucash_core.Book = session.book
    root_account: gnucash.gnucash_core.Account = book.get_root_account()

From that "root" Account we can either directly access the account we are interested on.

import gnucash

with gnucash.Session("sample-file.gnucash") as session:
    book: gnucash.gnucash_core.Book = session.book
    root_account: gnucash.gnucash_core.Account = book.get_root_account()
    wallet: gnucash.gnucash_core.Account = root_account.lookup_by_full_name("Assets.Current Assets.Wallet")
    print("Wallet:", wallet.get_full_name())
Wallet: Assets.Current Assets.Wallet

We can also iterate over an account's children:

import gnucash

with gnucash.Session("sample-file.gnucash") as session:
    book: gnucash.gnucash_core.Book = session.book
    root_account: gnucash.gnucash_core.Account = book.get_root_account()
    for account in root_account.get_children():
        print("Sub-account:", account.get_full_name())
Sub-account: Assets
Sub-account: Income
Sub-account: Expenses

Let's access the account with lookup_by_full_name and pull it's Splits. A Transaction is composed of Splits which are "money in" an account or "money out" an account. Normally your Transactions will have 2 Split (one for the money "out" and one for the money "in").

Let's then list the Splits in the account, get it's amount, and the description for the transaction:

import gnucash

with gnucash.Session("sample-file.gnucash") as session:
    book: gnucash.gnucash_core.Book = session.book
    root_account: gnucash.gnucash_core.Account = book.get_root_account()
    wallet: gnucash.gnucash_core.Account = root_account.lookup_by_full_name("Assets.Current Assets.Wallet")
    for split in wallet.GetSplitList():
        print("{:7.2f} - Concept - {}".format(float(split.GetAmount()), split.GetParent().GetDescription()))
   3.00 - Concept - Received allowance
  -2.00 - Concept - Bought gummies
  -1.00 - Concept - Savings to the Piggy Bank

That's the general idea for reading transactions. Running this code (without with) in an IPython session is a good idea to have some objects to explore. Just remember to run session.end() when you're done.

import gnucash

session = gnucash.Session("sample-file.gnucash")

book: gnucash.gnucash_core.Book = session.book
root_account: gnucash.gnucash_core.Account = book.get_root_account()
wallet: gnucash.gnucash_core.Account = root_account.lookup_by_full_name("Assets.Current Assets.Wallet")
splits = wallet.GetSplitList()
split: gnucash.gnucash_core.Split = splits[0]
print("{:7.2f} - Concept - {}".format(float(split.GetAmount()), split.GetParent().GetDescription()))

# Remember to do session.end()

An IPython window showing the code executed and interactive autocompletion of the split object

Adding a new transaction

(There's useful documentation on this, but written for C++)

Ok, now we know how to read the existing transactions, but how about creating a new one? Let's create a simple transaction to record the buy of more of those gummies.

A transaction has to be created with a book as base.

import gnucash

session = gnucash.Session("sample-file.gnucash")
book: gnucash.gnucash_core.Book = session.book
root_account: gnucash.gnucash_core.Account = book.get_root_account()
tx = gnucash.Transaction(book)

Then, we can set the base information for the transaction.

tx.BeginEdit()

# Currency will be EUR
currency = book.get_table().lookup('ISO4217', "EUR")
tx.SetCurrency(currency)

# time where the transaction is saved on the book
import datetime
tx.SetDateEnteredSecs(datetime.datetime.now())

# Time when the transaction happened
transaction_time = datetime.datetime.now()
tx.SetDatePostedSecs(transaction_time)
tx.SetDate(transaction_time.date().day, transaction_time.date().month, transaction_time.date().year)

# Description for the transaction
tx.SetDescription("Bough more gummies")

# Prepare some common data
tx_cost = 1  # 1€
tx_value = round(tx_cost * currency.get_fraction())

Then, the transaction has to declare it's two splits, the origin and the destination.

# Origin of the transaction
split_from = gnucash.Split(book)

# Link split to transaction
split_from.SetParent(tx)

# Set origin account
from_account: gnucash.gnucash_core.Account = root_account.lookup_by_full_name("Assets.Current Assets.Savings")
split_from.SetAccount(from_account)

# Set the value of the transaction.
# note that this is negative, to represent outgoing money
#
# Convert the transaction to the appropriate fixed-point-like representation
# EUR, for example, is expressed on cents
split_from.SetValue(gnucash.GncNumeric(-tx_value, currency.get_fraction()))

# If we're working with a single currency in this transaction, Value
#   and Amount will be the same
split_from.SetAmount(gnucash.GncNumeric(-tx_value, currency.get_fraction()))

Do the same for the destination of the transaction

# Destination of the transaction
split_to = gnucash.Split(book)

# Link split to transaction
split_to.SetParent(tx)

# Set origin account
to_account: gnucash.gnucash_core.Account = root_account.lookup_by_full_name("Expenses.Sweets")
split_to.SetAccount(to_account)

# Set the value of the transaction.
split_to.SetValue(gnucash.GncNumeric(tx_value, currency.get_fraction()))
split_to.SetAmount(gnucash.GncNumeric(tx_value, currency.get_fraction()))

And finally, we can "commit" the transaction and save the session's data.

# Commit transaction
tx.CommitEdit()

# Update gnucash file
session.save()

# Close file
session.end()

Terminal screenshot showing the execution of the code described above

This will produce a new transaction row

Transaction row from the Assets.Savings account showing a movement to Expenses:Sweets

And that's it! Hopefully it was a useful way to look into how to interact with GnuCash from Python.

References