Hello all,
I think it's time for a primer on using Python to extend your Ledger
experience. But first, a word must be said about Ledger's data model, so that
other things make sense later.
------------------------------------------------------------------------------
# Basic data traversal
Every interaction with Ledger happens in the context of a Session. Even if
you don't create a session manually, one is created for you by the top-level
interface functions. The Session is where objects live like the Commodity's
that Amount's refer to.
The make a Session useful, you must read a Journal into it, using the function
`read_journal`. This reads Ledger data from the given file, populates a
Journal object within the current Session, and returns a reference to that
Journal object.
Within the Journal live all the Transaction's, Posting's, and other objects
related to your data. There are also AutomatedTransaction's and
PeriodicTransaction's, etc.
Here is how you would traverse all the postings in your data file:
import ledger
for xact in ledger.read_journal("sample.dat").xacts:
for post in xact.posts:
print "Transferring %s to/from %s" % (post.amount, post.account)
------------------------------------------------------------------------------
# Raw vs. Cooked
Ledger data exists in one of two forms: raw and cooked. Raw objects are what
you get from a traversal like the above, and represent exactly what was seen
in the data file. Consider this journal:
= true
(Assets:Cash) $100
2012-03-01 KFC
Expenses:Food $100
Assets:Credit
In this case, the *raw* regular transaction in this file is:
2012-03-01 KFC
Expenses:Food $100
Assets:Credit
While the cooked form is:
2012-03-01 KFC
Expenses:Food $100
Assets:Credit $-100
(Assets:Cash) $100
So the easy way to think about raw vs. cooked is that raw is the unprocessed
data, and cooked has had all considerations applied.
When you traverse a Journal by iterating its transactions, you are generally
looking at raw data. In order to look at cooked data, you must generate a
report of some kind by querying the journal:
for post in ledger.read_journal("sample.dat").query("food"):
print "Transferring %s to/from %s" % (post.amount, post.account)
The reason why queries iterate over postings instead of transactions is that
queries often return only a "slice" of the transactions they apply to. You
can always get at a matching posting's transaction by looking at its "xact"
member:
last_xact = None
for post in ledger.read_journal("sample.dat").query(""):
if post.xact != last_xact:
for post in post.xact.posts:
print "Transferring %s to/from %s" % (post.amount,
post.account)
last_xact = post.xact
This query ends up reporting every cooked posting in the Journal, but does it
transaction-wise. It relies on the fact that an unsorted report returns
postings in the exact order they were parsed from the journal file.
------------------------------------------------------------------------------
# Queries
The Journal.query() method accepts every argument you can specify on the
command-line, including --options.
Since a query "cooks" the journal it applies to, only one query may be active
for that journal at a given time. Once the query object is gone (after the
for loop), then the data reverts back to its raw state.
------------------------------------------------------------------------------
# Embedded Python
Can you embed Python into your data files using the 'python' directive:
python
import so
def check_path(path_value):
print "%s => %s" % (str(path_value),
os.path.isfile(str(path_value)))
return os.path.isfile(str(path_value))
tag PATH
assert check_path(value)
2012-02-29 KFC
; PATH: somebogusfile.dat
Expenses:Food $20
Assets:Cash
Any Python functions you define this way become immediately available as
valexpr functions.
------------------------------------------------------------------------------
# Amounts
When numbers come from Ledger, like post.amount, the type of the value is
Amount. It can be used just like an ordinary number, except that addition
and subtraction are restricted to amounts with the same commodity. If you
need to create sums of multiple commodities, use a Balance. For example:
total = Balance()
for post in ledger.read_journal("sample.dat").query(""):
total += post.amount
print total
John