Added more documentation to python/demo.py
This commit is contained in:
parent
73c3aa324b
commit
b00e7ac19a
2 changed files with 239 additions and 60 deletions
285
python/demo.py
Normal file → Executable file
285
python/demo.py
Normal file → Executable file
|
|
@ -1,91 +1,264 @@
|
|||
#!/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:
|
||||
print "FAILED: %s != %s" % (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':
|
||||
|
||||
pool = ledger.commodity_pool
|
||||
comms = ledger.commodities
|
||||
|
||||
usd = pool.find_or_create('$')
|
||||
eur = pool.find_or_create('EUR')
|
||||
xcd = pool.find_or_create('XCD')
|
||||
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('$', pool['$'].symbol)
|
||||
|
||||
assert not pool.find('CAD')
|
||||
assert not pool.has_key('CAD')
|
||||
assert not 'CAD' in pool
|
||||
|
||||
# There are a few built-in commodities: null, %, h, m and s
|
||||
assertEqual([u'', u'$', u'%', u'EUR', u'XCD',
|
||||
u'h', u'm', u's'], sorted(pool.keys()))
|
||||
|
||||
for symbol in pool.iterkeys(): pass
|
||||
for commodity in pool.itervalues(): pass
|
||||
|
||||
# jww (2009-11-19): Not working: missing conversion from std::pair
|
||||
#for symbol, commodity in pool.iteritems(): pass
|
||||
#for symbol, commodity in pool: pass
|
||||
|
||||
# This creates a price exchange entry, trading EUR for $0.77 each at the
|
||||
# current time.
|
||||
pool.exchange(eur, ledger.Amount('$0.77'))
|
||||
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:
|
||||
|
||||
# When two amounts are multipied or divided, the result carries the commodity
|
||||
# of the first term. So, 1 EUR / $0.77 == roughly 1.2987 EUR
|
||||
amt = ledger.Amount('$100.12')
|
||||
market = ((ledger.Amount('1 EUR') / ledger.Amount('$0.77')) * amt)
|
||||
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')
|
||||
|
||||
# An amount's "precision" is a notional thing only. Since Ledger uses
|
||||
# rational numbers throughout, and only renders to decimal form for printing
|
||||
# to the user, the meaning of amt.precision should not be relied on as
|
||||
# meaningful. It only controls how much precision unrounded numbers (those
|
||||
# for which keep_precision is True, and thus that ignore display_precision)
|
||||
# are rendered into strings. This is the case, btw, for all uncommoditized
|
||||
# amounts.
|
||||
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)
|
||||
|
||||
assertEqual('$-100.12', str(amt.negated())) # negate the amount
|
||||
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(market, amt.value(eur)) # find present market value
|
||||
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
|
||||
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()
|
||||
assert amt.fits_in_long() # there is no `fits_in_double'
|
||||
assertEqual(100, amt.to_long())
|
||||
|
||||
amt2 = ledger.Amount('$100.12 {140 EUR}')
|
||||
# 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(ledger.KeepDetails()))
|
||||
assertEqual(amt, amt2.strip_annotations())
|
||||
|
||||
# jww (2009-11-19): Not working: missing conversion from optional<amount_t>
|
||||
#assertEqual(ledger.Amount('20 EUR'), amt.annotation.price)
|
||||
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')
|
||||
|
||||
|
|
|
|||
|
|
@ -224,6 +224,14 @@ namespace {
|
|||
return comm.strip_annotations(keep);
|
||||
}
|
||||
|
||||
boost::optional<amount_t> py_price(annotation_t& ann) {
|
||||
return ann.price;
|
||||
}
|
||||
boost::optional<amount_t> py_set_price(annotation_t& ann,
|
||||
const boost::optional<amount_t>& price) {
|
||||
return ann.price = price;
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
void export_commodity()
|
||||
|
|
@ -295,7 +303,7 @@ void export_commodity()
|
|||
|
||||
map_value_type_converter<commodity_pool_t::commodities_map>();
|
||||
|
||||
scope().attr("commodity_pool") = commodity_pool_t::current_pool;
|
||||
scope().attr("commodities") = commodity_pool_t::current_pool;
|
||||
|
||||
scope().attr("COMMODITY_STYLE_DEFAULTS") = COMMODITY_STYLE_DEFAULTS;
|
||||
scope().attr("COMMODITY_STYLE_SUFFIXED") = COMMODITY_STYLE_SUFFIXED;
|
||||
|
|
@ -375,9 +383,7 @@ void export_commodity()
|
|||
.def("drop_flags", &supports_flags<>::drop_flags)
|
||||
#endif
|
||||
|
||||
.add_property("price",
|
||||
make_getter(&annotation_t::price),
|
||||
make_setter(&annotation_t::price))
|
||||
.add_property("price", py_price, py_set_price)
|
||||
.add_property("date",
|
||||
make_getter(&annotation_t::date),
|
||||
make_setter(&annotation_t::date))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue