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

Reply via email to