summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2009-11-20 21:33:23 -0500
committerJohn Wiegley <johnw@newartisans.com>2009-11-20 21:33:23 -0500
commit117dddabd4f883de4f464821f9567d889a6fa449 (patch)
tree602c8c5e72b81ffc9bcf01bbaed5e2875d0e60a0 /python
parentcc9110a43a1e2d006de8d24a84bbfb2c6918cf33 (diff)
parent4e6ec09e4d2a69dcb06627e44512980b09561448 (diff)
downloadfork-ledger-117dddabd4f883de4f464821f9567d889a6fa449.tar.gz
fork-ledger-117dddabd4f883de4f464821f9567d889a6fa449.tar.bz2
fork-ledger-117dddabd4f883de4f464821f9567d889a6fa449.zip
Merge branch 'next'
Diffstat (limited to 'python')
-rwxr-xr-xpython/demo.py293
1 files changed, 293 insertions, 0 deletions
diff --git a/python/demo.py b/python/demo.py
new file mode 100755
index 00000000..7b4003f3
--- /dev/null
+++ b/python/demo.py
@@ -0,0 +1,293 @@
+#!/usr/bin/env python
+
+import sys
+from datetime import datetime
+
+# The following literate program will demonstrate, by example, how to use the
+# Ledger Python module to access your data and build custom reports using the
+# magic of Python.
+
+import ledger
+
+print "Welcome to the Ledger.Python demo!"
+
+# Some quick helper functions to help us assert various types of truth
+# throughout the script.
+
+def assertEqual(pat, candidate):
+ if pat != candidate:
+ raise Exception("FAILED: %s != %s" % (pat, candidate))
+ sys.exit(1)
+
+###############################################################################
+#
+# COMMODITIES
+#
+# Every amount in Ledger has a commodity, even if it is the "null commodity".
+# What's special about commodities are not just their symbol, but how they
+# alter the way amounts are displayed.
+#
+# For example, internally Ledger uses infinite precision rational numbers,
+# which have no decimal point. So how does it know that $1.00 / $0.75 should
+# be displayed as $1.33, and not with an infinitely repeating decimal? It
+# does it by consulting the commodity.
+#
+# Whenever an amount is encountered in your data file, Ledger observes how you
+# specified it:
+# - How many digits of precision did you use?
+# - Was the commodity name before or after the amount?
+# - Was the commodity separated from the amount by a space?
+# - Did you use thousands markers (1,000)?
+# - Did you use European-style numbers (1.000,00)?
+#
+# By tracking this information for each commodity, Ledger knows how you want
+# to see the amount in your reports. This way, dollars can be output as
+# $123.56, while stock options could be output as 10.113 AAPL.
+#
+# Your program can access the known set of commodities using the global
+# `ledger.commodities'. This object behaves like a dict, and support all of
+# the non-modifying dict protocol methods. If you wish to create a new
+# commodity without parsing an amount, you can use the method
+# `find_or_create':
+
+comms = ledger.commodities
+
+usd = comms.find_or_create('$')
+eur = comms.find_or_create('EUR')
+xcd = comms.find_or_create('XCD')
+
+assert not comms.find('CAD')
+assert not comms.has_key('CAD')
+assert not 'CAD' in comms
+
+# The above mentioned commodity display attributes can be set using commodity
+# display flags. This is not something you will usually be doing, however, as
+# these flags can be inferred correctly from a large enough set of sample
+# amounts, such as those found in your data file. If you live in Europe and
+# want all amounts to default to the European-style, set the static variable
+# `european_by_default'.
+
+eur.add_flags(ledger.COMMODITY_STYLE_EUROPEAN)
+assert eur.has_flags(ledger.COMMODITY_STYLE_EUROPEAN)
+assert not eur.has_flags(ledger.COMMODITY_STYLE_THOUSANDS)
+
+comms.european_by_default = True
+
+# There are a few built-in commodities: null, %, h, m and s. Normally you
+# don't need to worry about them, but they'll show up if you examine all the
+# keys in the commodities dict.
+
+assertEqual([u'', u'$', u'%', u'EUR', u'XCD', u'h', u'm', u's'],
+ sorted(comms.keys()))
+
+# All the styles of dict iteration are supported:
+
+for symbol in comms.iterkeys():
+ pass
+for commodity in comms.itervalues():
+ pass
+#for symbol, commodity in comms.iteritems():
+# pass
+#for symbol, commodity in comms:
+# pass
+
+# Another important thing about commodities is that they remember if they've
+# been exchanged for another commodity, and what the conversion rate was on
+# that date. You can record specific conversion rates for any date using the
+# `exchange' method.
+
+comms.exchange(eur, ledger.Amount('$0.77')) # Trade 1 EUR for $0.77
+comms.exchange(eur, ledger.Amount('$0.66'), datetime.now())
+
+# For the most part, however, you won't be interacting with commodities
+# directly, except maybe to look at their `symbol'.
+
+assertEqual('$', usd.symbol)
+assertEqual('$', comms['$'].symbol)
+
+###############################################################################
+#
+# AMOUNTS & BALANCES
+#
+# Ledger deals with two basic numerical values: Amount and Balance objects.
+# An Amount is an infinite-precision rational with an associated commodity
+# (even if it is the null commodity, which is called an "uncommoditized
+# amount"). A Balance is a collection of Amounts of differing commodities.
+#
+# Amounts support all the math operations you might expect of an integer,
+# except it carries a commodity. Let's take dollars for example:
+
+zero = ledger.Amount("$0")
+one = ledger.Amount("$1")
+oneb = ledger.Amount("$1")
+two = ledger.Amount("$2")
+three = ledger.Amount("3") # uncommoditized
+
+assert one == oneb # numeric equality, not identity
+assert one != two
+assert not zero # tests if it would *display* as a zero
+assert one < two
+assert one > zero
+
+# For addition and subtraction, only amounts of the same commodity may be
+# used, unless one of the amounts has no commodity at all -- in which case the
+# result uses the commodity of the other value. Adding $10 to 10 EUR, for
+# example, causes an ArithmeticError exception, but adding 10 to $10 gives
+# $20.
+
+four = ledger.Amount(two) # make a copy
+four += two
+assertEqual(four, two + two)
+assertEqual(zero, one - one)
+
+try:
+ two += ledger.Amount("20 EUR")
+ assert False
+except ArithmeticError:
+ pass
+
+# Use `number' to get the uncommoditized version of an Amount
+
+assertEqual(three, (two + one).number())
+
+# Multiplication and division does supports Amounts of different commodities,
+# however:
+# - If either amount is uncommoditized, the result carries the commodity of
+# the other amount.
+# - Otherwise, the result always carries the commodity of the first amount.
+
+five = ledger.Amount("5 CAD")
+
+assertEqual(one, two / two)
+assertEqual(five, (five * ledger.Amount("$2")) - ledger.Amount("5"))
+
+# An amount's commodity determines the decimal precision it's displayed with.
+# However, this "precision" is a notional thing only. You can tell an amount
+# to ignore its display precision by setting `keep_precision' to True.
+# (Uncommoditized amounts ignore the value of `keep_precision', and assume it
+# is always True). In this case, Ledger does its best to maintain maximal
+# precision by watching how the Amount is used. That is, 1.01 * 1.01 yields a
+# precision of 4. This tracking is just a best estimate, however, since
+# internally Ledger never uses floating-point values.
+
+amt = ledger.Amount('$100.12')
+mini = ledger.Amount('0.00045')
+
+assert not amt.keep_precision
+
+assertEqual(5, mini.precision)
+assertEqual(5, mini.display_precision) # display_precision == precision
+assertEqual(2, amt.precision)
+assertEqual(2, amt.display_precision)
+
+mini *= mini
+amt *= amt
+
+assertEqual(10, mini.precision)
+assertEqual(10, mini.display_precision)
+assertEqual(4, amt.precision)
+assertEqual(2, amt.display_precision)
+
+# There are several other supported math operations:
+
+amt = ledger.Amount('$100.12')
+market = ((ledger.Amount('1 EUR') / ledger.Amount('$0.77')) * amt)
+
+assertEqual(market, amt.value(eur)) # find present market value
+
+assertEqual('$-100.12', str(amt.negated())) # negate the amount
+assertEqual('$-100.12', str(- amt)) # negate it more simply
+assertEqual('$0.01', str(amt.inverted())) # reverse NUM/DEM
+assertEqual('$100.12', str(amt.rounded())) # round it to display precision
+assertEqual('$100.12', str(amt.truncated())) # truncate to display precision
+assertEqual('$100.00', str(amt.floored())) # floor it to nearest integral
+assertEqual('$100.12', str(abs(amt))) # absolute value
+assertEqual('$100.12', str(amt)) # render to a string
+assertEqual('100.12', amt.quantity_string()) # render quantity to a string
+assertEqual('100.12', str(amt.number())) # strip away commodity
+assertEqual(1, amt.sign()) # -1, 0 or 1
+assert amt.is_nonzero() # True if display amount nonzero
+assert not amt.is_zero() # True if display amount is zero
+assert not amt.is_realzero() # True only if value is 0/0
+assert not amt.is_null() # True if uninitialized
+
+# Amounts can also be converted the standard floats and integers, although
+# this is not recommend since it can lose precision.
+
+assertEqual(100.12, amt.to_double())
+assert amt.fits_in_long() # there is no `fits_in_double'
+assertEqual(100, amt.to_long())
+
+# Finally, amounts can be annotated to provide additional information about
+# "lots" of a given commodity. This example shows $100.12 that was purchased
+# on 2009/10/01 for 140 EUR. Lot information can be accessed through via the
+# Amount's `annotation' property. You can also strip away lot details to get
+# the underlying amount. If you want the total price of any Amount, by
+# multiplying by its per-unit lot price, call the `Amount.price' method
+# instead of the `Annotation.price' property.
+
+amt2 = ledger.Amount('$100.12 {140 EUR} [2009/10/01]')
+
+assert amt2.has_annotation()
+assertEqual(amt, amt2.strip_annotations())
+
+assertEqual(ledger.Amount('140 EUR'), amt2.annotation.price)
+assertEqual(ledger.Amount('14016,8 EUR'), amt2.price()) # european amount!
+
+###############################################################################
+#
+# VALUES
+#
+# As common as Amounts and Balances are, there is a more prevalent numeric
+# type you will encounter when generating reports: Value objects. A Value is
+# a variadic type that can be any of the following types:
+# - Amount
+# - Balance
+# - boolean
+# - integer
+# - datetime
+# - date
+# - string
+# - regex
+# - sequence
+#
+# The reason for the variadic type is that it supports dynamic self-promotion.
+# For example, it is illegal to add two Amounts of different commodities, but
+# it is not illegal to add two Value amounts of different commodities. In the
+# former case an exception in raised, but in the latter the Value simply
+# promotes itself to a Balance object to make the addition valid.
+#
+# Values are not used by any of Ledger's data objects (Journal, Transaction,
+# Posting or Account), but they are used extensively by value expressions.
+
+val = ledger.Value('$100.00')
+
+assert val.is_amount()
+assertEqual('$', val.to_amount().commodity.symbol)
+
+# JOURNALS
+
+#journal.find_account('')
+#journal.find_or_create_account('')
+
+# ACCOUNTS
+
+#account.name
+#account.fullname()
+#account.amount
+#account.total
+
+# TRANSACTIONS
+
+#txn.payee
+
+# POSTINGS
+
+#post.account
+
+# REPORTING
+
+#journal.collect('-M food')
+#journal.collect_accounts('^assets ^liab ^equity')
+
+print 'Demo completed successfully.'