Tutorial : using existing objects

Opening an existing Book

To open an existing GnuCash document (and get the related Book), use the open_book() function:

import piecash

# for a sqlite3 document
book = piecash.open_book("existing_file.gnucash")

# or through an URI connection string for sqlite3
book = piecash.open_book(uri_conn="sqlite:///existing_file.gnucash")
# or for postgres
book = piecash.open_book(uri_conn="postgres://user:passwd@localhost/existing_gnucash_db")

The documents are open as readonly per default. To allow RW access, specify explicitly readonly=False as:

book = piecash.open_book("existing_file.gnucash", readonly=False)

When opening in full access (readonly=False), piecash will automatically create a backup file named filename.piecash_YYYYMMDD_HHMMSS with the original file. To avoid creating the backup file, specificy backup=False as:

book = piecash.open_book("existing_file.gnucash", readonly=False, backup=False)

To force opening the file even through there is a lock on it, use the open_if_lock=True argument:

book = piecash.open_book("existing_file.gnucash", open_if_lock=True)

Access to objects

Once a GnuCash book is opened through a piecash.core.book.Book, GnuCash objects can be accessed through two different patterns:

The object model

In this mode, we access elements through their natural relations, starting from the book and jumping from one object to the other:

In [1]: book = open_book(gnucash_books + "default_book.gnucash")

In [2]: book.root_account # accessing the root_account
Out[2]: Account<[EUR]>

In [3]: # looping through the children accounts of the root_account
   ...: for acc in book.root_account.children:
   ...:     print(acc)
   ...: 
Account<Assets[EUR]>
Account<Liabilities[EUR]>
Account<Income[EUR]>
Account<Expenses[EUR]>
Account<Equity[EUR]>

# accessing children accounts
In [5]: 
   ...: root = book.root_account              # select the root_account
   ...: assets = root.children(name="Assets")   # select child account by name
   ...: cur_assets = assets.children[0]         # select child account by index
   ...: cash = cur_assets.children(type="CASH") # select child account by type
   ...: print(cash)
   ...: 
Account<Assets:Current Assets:Cash in Wallet[EUR]>

In [10]: # get the commodity of an account
   ....: commo = cash.commodity
   ....: print(commo)
   ....: 
Commodity<CURRENCY:EUR>

In [13]: # get first ten accounts linked to the commodity commo
   ....: for acc in commo.accounts[:10]:
   ....:     print(acc)
   ....: 
Account<[EUR]>
Account<Assets[EUR]>
Account<Assets:Current Assets[EUR]>
Account<Assets:Current Assets:Checking Account[EUR]>
Account<Assets:Current Assets:Savings Account[EUR]>
Account<Assets:Current Assets:Cash in Wallet[EUR]>
Account<Liabilities[EUR]>
Account<Liabilities:Credit Card[EUR]>
Account<Income[EUR]>
Account<Income:Bonus[EUR]>

The “table” access

In this mode, we access elements through collections directly accessible from the book:

In [15]: book = open_book(gnucash_books + "default_book.gnucash")

# accessing all accounts
In [16]: book.accounts
Out[16]: 
[Account<Assets[EUR]>,
 Account<Assets:Current Assets[EUR]>,
 Account<Assets:Current Assets:Checking Account[EUR]>,
 Account<Assets:Current Assets:Savings Account[EUR]>,
 Account<Assets:Current Assets:Cash in Wallet[EUR]>,
 Account<Liabilities[EUR]>,
 Account<Liabilities:Credit Card[EUR]>,
 Account<Income[EUR]>,
 Account<Income:Bonus[EUR]>,
 Account<Income:Gifts Received[EUR]>,
 Account<Income:Interest Income[EUR]>,
 Account<Income:Interest Income:Checking Interest[EUR]>,
 Account<Income:Interest Income:Other Interest[EUR]>,
 Account<Income:Interest Income:Savings Interest[EUR]>,
 Account<Income:Other Income[EUR]>,
 Account<Income:Salary[EUR]>,
 Account<Expenses[EUR]>,
 Account<Expenses:Adjustment[EUR]>,
 Account<Expenses:Auto[EUR]>,
 Account<Expenses:Auto:Fees[EUR]>,
 Account<Expenses:Auto:Gas[EUR]>,
 Account<Expenses:Auto:Parking[EUR]>,
 Account<Expenses:Auto:Repair and Maintenance[EUR]>,
 Account<Expenses:Bank Service Charge[EUR]>,
 Account<Expenses:Books[EUR]>,
 Account<Expenses:Cable[EUR]>,
 Account<Expenses:Charity[EUR]>,
 Account<Expenses:Clothes[EUR]>,
 Account<Expenses:Computer[EUR]>,
 Account<Expenses:Dining[EUR]>,
 Account<Expenses:Education[EUR]>,
 Account<Expenses:Entertainment[EUR]>,
 Account<Expenses:Entertainment:Music/Movies[EUR]>,
 Account<Expenses:Entertainment:Recreation[EUR]>,
 Account<Expenses:Entertainment:Travel[EUR]>,
 Account<Expenses:Gifts[EUR]>,
 Account<Expenses:Groceries[EUR]>,
 Account<Expenses:Hobbies[EUR]>,
 Account<Expenses:Insurance[EUR]>,
 Account<Expenses:Insurance:Auto Insurance[EUR]>,
 Account<Expenses:Insurance:Health Insurance[EUR]>,
 Account<Expenses:Insurance:Life Insurance[EUR]>,
 Account<Expenses:Laundry/Dry Cleaning[EUR]>,
 Account<Expenses:Medical Expenses[EUR]>,
 Account<Expenses:Miscellaneous[EUR]>,
 Account<Expenses:Online Services[EUR]>,
 Account<Expenses:Phone[EUR]>,
 Account<Expenses:Public Transportation[EUR]>,
 Account<Expenses:Subscriptions[EUR]>,
 Account<Expenses:Supplies[EUR]>,
 Account<Expenses:Taxes[EUR]>,
 Account<Expenses:Taxes:Federal[EUR]>,
 Account<Expenses:Taxes:Medicare[EUR]>,
 Account<Expenses:Taxes:Other Tax[EUR]>,
 Account<Expenses:Taxes:Social Security[EUR]>,
 Account<Expenses:Taxes:State/Province[EUR]>,
 Account<Expenses:Utilities[EUR]>,
 Account<Expenses:Utilities:Electric[EUR]>,
 Account<Expenses:Utilities:Garbage collection[EUR]>,
 Account<Expenses:Utilities:Gas[EUR]>,
 Account<Expenses:Utilities:Water[EUR]>,
 Account<Equity[EUR]>,
 Account<Equity:Opening Balances[EUR]>]

# accessing all commodities
In [17]: book.commodities
Out[17]: [Commodity<CURRENCY:EUR>]

# accessing all transactions
In [18]: book.transactions
Out[18]: []

Each of these collections can be either iterated or accessed through some indexation or filter mechanism (return first element of collection satisfying some criteria(s)):

# iteration
In [19]: for acc in book.accounts:
   ....:     if acc.type == "ASSET": print(acc)
   ....: 
Account<Assets[EUR]>
Account<Assets:Current Assets[EUR]>

# indexation (not very meaningful)
In [20]: book.accounts[10]
Out[20]: Account<Income:Interest Income[EUR]>

# filter by name
In [21]: book.accounts(name="Garbage collection")
Out[21]: Account<Expenses:Utilities:Garbage collection[EUR]>

# filter by type
In [22]: book.accounts(type="EXPENSE")
Out[22]: Account<Expenses[EUR]>

# filter by fullname
In [23]: book.accounts(fullname="Expenses:Taxes:Social Security")
Out[23]: Account<Expenses:Taxes:Social Security[EUR]>

# filter by multiple criteria
In [24]: book.accounts(commodity=book.commodities[0], name="Gas")
Out[24]: Account<Expenses:Auto:Gas[EUR]>

The “SQLAlchemy” access (advanced users)

In this mode, we access elements through SQLAlchemy queries on the SQLAlchemy session:

# retrieve underlying SQLAlchemy session object
In [1]: session = book.session

# get all account with name >= "T"
In [2]: session.query(Account).filter(Account.name>="T").all()
Out[2]: 
[Account<Expenses:Entertainment:Travel[EUR]>,
 Account<Expenses:Taxes[EUR]>,
 Account<Expenses:Utilities[EUR]>,
 Account<Expenses:Utilities:Water[EUR]>,
 Account<>]

# display underlying query
In [3]: str(session.query(Account).filter(Account.name>="T"))
Out[3]: 'SELECT accounts.account_type AS accounts_account_type, accounts.commodity_scu AS accounts_commodity_scu, accounts.non_std_scu AS accounts_non_std_scu, accounts.placeholder AS accounts_placeholder, accounts.guid AS accounts_guid, accounts.name AS accounts_name, accounts.commodity_guid AS accounts_commodity_guid, accounts.parent_guid AS accounts_parent_guid, accounts.code AS accounts_code, accounts.description AS accounts_description, accounts.hidden AS accounts_hidden \nFROM accounts \nWHERE accounts.name >= :name_1'

Accounts

Accessing the accounts (piecash.core.account.Account):

from piecash import open_book

with open_book("gnucash_books/simple_sample.gnucash") as book:
    # accessing the root_account
    root = book.root_account
    print(root)

    # accessing the first children account of a book
    acc = root.children[0]
    print(acc)

    # accessing attributes of an account
    print("Account name={acc.name}\n"
          "        commodity={acc.commodity.namespace}/{acc.commodity.mnemonic}\n"
          "        fullname={acc.fullname}\n"
          "        type={acc.type}".format(acc=acc))

    # accessing all splits related to an account
    for sp in acc.splits:
        print("account <{}> is involved in transaction '{}'".format(acc.fullname, sp.transaction.description))

Commodities and Prices

The list of all commodities in the book can be retrieved via the commodities attribute:

# all commodities
print(book.commodities)

cdty = book.commodities[0]

# accessing attributes of a commodity
print("Commodity namespace={cdty.namespace}\n"
      "          mnemonic={cdty.mnemonic}\n"
      "          cusip={cdty.cusip}\n"
      "          fraction={cdty.fraction}".format(cdty=cdty))

The prices (piecash.core.commodity.Price) of a commodity can be iterated through the prices attribute:

# loop on the prices
for pr in cdty.prices:
    print("Price date={pr.date}"
          "      value={pr.value} {pr.currency.mnemonic}/{pr.commodity.mnemonic}".format(pr=pr))

Transactions and Splits

The list of all transactions in the book can be retrieved via the transactions attribute:

In [1]: book = open_book(gnucash_books + "book_schtx.gnucash", open_if_lock=True)

# all transactions (including transactions part of a scheduled transaction description)
In [2]: for tr in book.transactions:
   ...:     print(tr)
   ...: 
Transaction<[EUR] 'Monthly utility bill' on 2015-01-02>
Transaction<[EUR] 'Insurance' on 2015-01-02>
Transaction<[EUR] 'Monthly utility bill' on 2013-12-31 (from sch tx)>
Transaction<[EUR] 'Monthly utility bill' on 2014-02-02 (from sch tx)>
Transaction<[EUR] 'Monthly utility bill' on 2014-03-02 (from sch tx)>
Transaction<[EUR] 'Monthly utility bill' on 2014-03-31 (from sch tx)>
Transaction<[EUR] 'Monthly utility bill' on 2014-04-30 (from sch tx)>
Transaction<[EUR] 'Monthly utility bill' on 2014-06-01 (from sch tx)>
Transaction<[EUR] 'Monthly utility bill' on 2014-06-30 (from sch tx)>
Transaction<[EUR] 'Monthly utility bill' on 2014-07-31 (from sch tx)>
Transaction<[EUR] 'Monthly utility bill' on 2014-08-31 (from sch tx)>
Transaction<[EUR] 'Monthly utility bill' on 2014-09-30 (from sch tx)>
Transaction<[EUR] 'Monthly utility bill' on 2014-11-02 (from sch tx)>
Transaction<[EUR] 'Monthly utility bill' on 2014-11-30 (from sch tx)>
Transaction<[EUR] 'Monthly utility bill' on 2014-12-31 (from sch tx)>
Transaction<[EUR] 'Insurance' on 2013-05-31 (from sch tx)>
Transaction<[EUR] 'Insurance' on 2014-05-29 (from sch tx)>
Transaction<[EUR] 'Opening balance' on 2013-01-02>
Transaction<[EUR] 'Monthly utility bill' on 2015-02-01 (from sch tx)>
Transaction<[EUR] 'Monthly utility bill' on 2015-03-01 (from sch tx)>
Transaction<[EUR] 'Monthly utility bill' on 2015-03-31 (from sch tx)>
Transaction<[EUR] 'Monthly utility bill' on 2015-04-30 (from sch tx)>
Transaction<[EUR] 'Monthly utility bill' on 2015-05-31 (from sch tx)>
Transaction<[EUR] 'Monthly utility bill' on 2015-06-30 (from sch tx)>
Transaction<[EUR] 'Monthly utility bill' on 2015-08-02 (from sch tx)>
Transaction<[EUR] 'Monthly utility bill' on 2015-08-31 (from sch tx)>
Transaction<[EUR] 'Monthly utility bill' on 2015-09-30 (from sch tx)>
Transaction<[EUR] 'Monthly utility bill' on 2015-11-01 (from sch tx)>
Transaction<[EUR] 'Insurance' on 2015-05-31 (from sch tx)>
Transaction<[EUR] 'test' on 2015-11-17>
Transaction<[EUR] 'salary' on 2013-12-31>
Transaction<[EUR] 'salary' on 2014-12-31>

# selecting first transaction generated from a scheduled transaction
In [3]: tr = [ tr for tr in book.transactions if tr.scheduled_transaction ][0]

For a given transaction, the following attributes are accessible:

# accessing attributes of a transaction
In [1]: print("Transaction description='{tr.description}'\n"
   ...:       "            currency={tr.currency}\n"
   ...:       "            post_date={tr.post_date}\n"
   ...:       "            enter_date={tr.enter_date}".format(tr=tr))
   ...: 
Transaction description='Monthly utility bill'
            currency=Commodity<CURRENCY:EUR>
            post_date=2013-12-31 23:00:00+00:00
            enter_date=2015-01-03 08:18:13+00:00

# accessing the splits of the transaction
In [2]: tr.splits
Out[2]: 
[Split<Account<Assets:Current Assets:Checking Account[EUR]> -70 EUR>,
 Split<Account<Expenses:Utilities:Electric[EUR]> 30 EUR>,
 Split<Account<Expenses:Utilities:Gas[EUR]> 40 EUR>]

# accessing the scheduled transaction
In [3]: [ sp for sp in tr.scheduled_transaction.template_account.splits]
Out[3]: 
[SplitTemplate<Account<Assets:Current Assets:Checking Account[EUR]> credit=70 >,
 SplitTemplate<Account<Expenses:Utilities:Electric[EUR]>  debit=30>,
 SplitTemplate<Account<Expenses:Utilities:Gas[EUR]>  debit=40>]

Other objects

In fact, any object can be retrieved from the session through a generic get(**kwargs) method:

from piecash import Account, Commodity, Budget, Vendor

# accessing specific objects through the get method
acc = book.get(Account, name="Asset", parent=book.root_account)
cdty = book.get(Commodity, namespace="CURRENCY", mnemonic="EUR")
bdgt = book.get(Budget, name="my first budget")
invoice = book.get(Vendor, name="Looney")

If you know SQLAlchemy, you can get access to the underlying Session as book.session and execute queries using the piecash classes:

from piecash import Account, Commodity, Budget, Vendor

# get the SQLAlchemy session
session = book.session

# loop through all invoices
for invoice in session.query(Invoice).all():
    print(invoice.notes)

Note

Easy access to objects from piecash.business and piecash.budget could be given directly from the session in future versions if deemed useful.

Working with slots

With regard to slots, GnuCash objects and Frames behave as dictionaries and all values are automatically converted back and forth to python objects:

In [1]: import datetime, decimal

In [2]: book = create_book()

# retrieve list of slots
In [3]: print(book.slots)
[<SlotGUID default-currency=Commodity<CURRENCY:EUR>>]

# set slots
In [4]: book["myintkey"] = 3

In [5]: book["mystrkey"] = "hello"

In [6]: book["myboolkey"] = True

In [7]: book["mydatekey"] = datetime.datetime.today().date()

In [8]: book["mydatetimekey"] = datetime.datetime.today()

In [9]: book["mynumerickey"] = decimal.Decimal("12.34567")

In [10]: book["account"] = book.root_account

# iterate over all slots
In [11]: for k, v in book.iteritems():
   ....:     print("slot={v} has key={k} and value={v.value} of type {t}".format(k=k,v=v,t=type(v.value)))
   ....: 
slot=<SlotGUID default-currency=Commodity<CURRENCY:EUR>> has key=default-currency and value=Commodity<CURRENCY:EUR> of type <class 'piecash.core.commodity.Commodity'>
slot=<SlotInt myintkey=3> has key=myintkey and value=3 of type <type 'int'>
slot=<SlotString mystrkey='hello'> has key=mystrkey and value=hello of type <type 'str'>
slot=<SlotInt myboolkey=True> has key=myboolkey and value=True of type <type 'bool'>
slot=<SlotDate mydatekey=datetime.date(2017, 2, 15)> has key=mydatekey and value=2017-02-15 of type <type 'datetime.date'>
slot=<SlotTime mydatetimekey=datetime.datetime(2017, 2, 15, 6, 40, 23, 997157)> has key=mydatetimekey and value=2017-02-15 06:40:23.997157 of type <type 'datetime.datetime'>
slot=<SlotNumeric mynumerickey=Decimal('12.34567')> has key=mynumerickey and value=12.34567 of type <class 'decimal.Decimal'>
slot=<SlotGUID account=Account<>> has key=account and value=Account<> of type <class 'piecash.core.account.Account'>

# delete a slot
In [12]: del book["myintkey"]

# delete all slots
In [13]: del book[:]

# create a key/value in a slot frames (and create them if they do not exist)
In [14]: book["options/Accounts/Use trading accounts"]="t"

# access a slot in frame in whatever notations
In [15]: s1=book["options/Accounts/Use trading accounts"]

In [16]: s2=book["options"]["Accounts/Use trading accounts"]

In [17]: s3=book["options/Accounts"]["Use trading accounts"]

In [18]: s4=book["options"]["Accounts"]["Use trading accounts"]

In [19]: assert s1==s2==s3==s4

Slots of type GUID use the name of the slot to do the conversion back and forth between an object and its guid. For these slots, there is an explicit mapping between slot names and object types.