Awesome
Rockefeller
Library for dealing with money and currency conversion in Python. It provides tools for storing currencies and exchange rates, converting from one currency to another and fetching exchange rates from different services.
Working with currencies
Currencies are represented by the Currency
class.
import rockefeller
usd = rockefeller.Currency(name='United States Dollar', code='USD',
numeric='840', symbol='$', exponent=2)
str(usd)
# => 'USD'
If you want to globally store a currency in your program, you first need to support it.
import rockefeller
usd = rockefeller.Currency(name='United States Dollar', code='USD',
numeric='840', symbol='$', exponent=2)
# You only have access to the currency stored in ``usd`` variable
rockefeller.Currency.USD
# => None
# Globally support the currency (See `setting currency store`)
usd.support()
# Now you can access it directly via the ``Currency`` class
rockefeller.Currency.USD
# => Currency(code='USD', ...)
rockefeller.Currency.get('USD')
# => Currency(code='USD', ...)
usd == rockefeller.Currency.USD
# => True
usd is rockefeller.Currency.USD
# => False
Note that there's no currency preloaded or stored by default, is up to you to store the currencies your application is going to support before working with them.
The default store
used by the Currency
class is MemoryCurrency
which stores the supported currency just in memory. If you need to store them
in other place see the section Currency Store.
Exchange rates
Exchange rates between currencies are represented through the ExchangeRate
class but you can add and retrieve exchange rates directly like this:
import rockefeller
eur = rockefeller.Currency(name='Euro', code='EUR', numeric='978',
symbol=u'€', exponent=2)
clp = rockefeller.Currency(name='Chilean Peso', code='CLP', numeric='152',
symbol='$', exponent=0)
rockefeller.get_exchange_rate(eur, clp)
# => None
# Store exchange rate for EUR => CLP
rockefeller.add_exchange_rate(eur, clp, 604.10)
rockefeller.get_exchange_rate(eur, clp)
# => Decimal('604.10')
# You can also get the inverse even if not explicitly defined
rockefeller.get_exchange_rate(clp, eur)
# => Decimal('0.001655355073663300778016884622')
rate == 604.10
# => False (604.10 doesn't have an exact float representation)
float(rate) == 604.10
# => True
Note The default store
used by the ExchangeRate
class is
MemoryExchangeRates
which stores the rates just in memory. If you need to
store them in other place see the section Exchange Rates Store.
Money
For working with currencies and amounts of it there's the convenient Money
class.
money = rockefeller.Money(amount=100.235, currency=rockefeller.Currency.USD)
Money arithmetic
Sum
(rockefeller.Money(100, rockefeller.Currency.USD) +
rockefeller.Money(100, rockefeller.Currency.USD))
# => Money(200, rockefeller.Currency.USD)
Subtraction
(rockefeller.Money(80, rockefeller.Currency.USD) -
rockefeller.Money(100, rockefeller.Currency.USD))
# => Money(-20, rockefeller.Currency.USD)
Subtraction (saturating)
rockefeller.Money(80, rockefeller.Currency.USD).remove(
rockefeller.Money(100, rockefeller.Currency.USD))
# => Money(0, rockefeller.Currency.USD)
Multiplication
(rockefeller.Money(10, rockefeller.Currency.USD) *
rockefeller.Money(10, rockefeller.Currency.USD))
# => Money(100, rockefeller.Currency.USD)
Division and Floor Division
(rockefeller.Money(100, rockefeller.Currency.USD) /
rockefeller.Money(100, rockefeller.Currency.USD))
# => Money(1, rockefeller.Currency.USD)
(rockefeller.Money(100, rockefeller.Currency.USD) //
rockefeller.Money(100, rockefeller.Currency.USD))
# => Money(1, rockefeller.Currency.USD)
Division and Remainder (divmod)
divmod(rockefeller.Money(25, rockefeller.Currency.USD),
rockefeller.Money(10, rockefeller.Currency.USD))
# => Money(2, rockefeller.Currency.USD), Money(5, rockefeller.Currency.UDS)
Rounding
usd = rockefeller.Money(25.1000, rockefeller.Currency.USD)
usd.rounded_amount
# => decimal.Decimal('25.10')
Float rounding using currency's exponent
usd_money = rockefeller.Money(amount=100.235, currency=rockefeller.Currency.USD)
clp_money = rockefeller.Money(amount=60551.984324, currency=rockefeller.Currency.CLP)
usd_money.amount
# => Decimal('100.235')
clp_money.amount
# => Decimal('60551.984324')
float(usd_money)
# => 100.24
float(clp_money)
# = > 60552
String representation
u'$100.24' == unicode(usd_money)
# => True
Equality
usd_money == rockefeller.Money(amount=100.235, currency=rockefeller.Currency.USD)
# => True
Conversion between currencies
import rockefeller
usd = rockefeller.Money(amount=100, currency=rockefeller.Currency.USD)
eur = rockefeller.Money(amount=78, currency=rockefeller.Currency.EUR)
rockefeller.add_exchange_rate(usd, eur, .78)
usd.exchange_to(rockefeller.Currency.EUR)
# => Money(78, rockefeller.Currency.EUR)
eur.exchange_to(rockefeller.Currency.USD)
# => Money(100, rockefeller.Currency.USD)
Conversion between currencies using indirection
Is common that third-party exchange-rate services gives you the rates of each currency relative to a common one. Take for example, openexchangerates.org that returns all rates relative to USD Dollars.
/* latest.json (7 mins ago) */
{
"timestamp": 1364680868,
"base": "USD",
"rates": {
"AED": 3.6729,
"AFN": 53.0133,
"ALL": 109.122501,
"AMD": 421.240004,
/* 164 currencies */
"YER": 214.800258,
"ZAR": 9.233264,
"ZMK": 5227.108333,
"ZMW": 5.3855,
"ZWL": 322.322775
}
}
This is how the Money
class works when you try to convert currency 1 into
currency 2:
- get_exchange_rate(currency1, currency2)
- if not found try the inverse: get_exchange_rate(currency2, currency1)
- if not found, try using the indirection currency
The indirection currency is a currency you set as the common/base currency to which the rest of the currency rates are related.
So, let's say we have the following:
rockefeller.add_exchange_rate(usd, eur, .78)
rockefeller.add_exchange_rate(usd, clp, 472.03735)
And you want to get the exchange rate from eur to clp:
rockefeller.Money(40, eur).exchange_to(clp)
# => ExchangeError(...)
It will raise a ExchangeError
since there's no direct relation between
eur -> clp or clp -> eur. Of course, this behavior is not desired
because storing all the rates between currencies will require 33,856
associations (taking into account that there's 184 different currencies).
The workaround to this problem is setting the indirection currency like this:
rockefeller.Money.indirection_currency = rockefeller.Currency.USD
With that in place, any time an exchange rate can't be found, the indirection currency will be checked, and if set, then this:
rockefeller.Money(40, eur).exchange_to(clp)
# => ExchangeError(...)
will be treated internally as this:
rockefeller.Money(40, eur).exchange_to(usd).exchange_to(clp)
# => Decimal('24177.16', rockefeller.Currency.CLP)
If you don't want to set a currency as the global indirection currency you can use a temporarily one like this:
rockefeller.Money(40, eur).exchange_to(clp, indirection_currency=usd)
# => Decimal('24177.16', rockefeller.Currency.CLP)
NOTICE
Take into account that the indirection currency is just a workaround used
by the Money
class to convert money from one currency into another, if you
try to get (not convert) the exchange rate between two unrelated currencies
using get_exchange_rate()
you will still get None
.
Currency Store
By default all supported currencies are stored in memory, so if your program finishes or you need the information of those currencies in another place not necessarily written in Python then you need a custom solution. But don't panic! you can instruct rockefeller to use the class you want to store the currencies, in order to do so you just need to create a class that implements the following interface:
class MyCurrencyStore:
def support(self, currency):
"""Store a currency.
:param currency: :class:`rockefeller.currency.Currency` instance.
"""
# you must implement this...
def get(self, code):
"""Get a currency by its code.
:param code: ISO 4217 currency code.
:return: :class:`rockefeller.currency.Currency` instance.
"""
# you must implement this...
With that in place, you just have to tell rockefeller to globally start using that store like this:
rockefeller.set_currency_store(MyCurrencyStore())
Or to locally use that store like this:
eur = rockefeller.Currency(name='Euro', code='EUR', numeric='978',
symbol=u'€', exponent=2)
my_store = MyCurrencyStore()
eur.support(store=my_store)
Exchange Rates Store
By default every exchange rate you add between currencies is stored in memory, so if your program stops or you need the information of those rates in another place not necessarily written in Python then you need a custom solution. The way you instruct rockefeller to use the class you want to store the exchange rates is by creating a class that implements the following interface:
class MyExchangeRateStore:
def add_exchange_rate(self, base_currency, currency, exchange_rate):
"""Store exchange rate of a one currency relatively to another one.
:param base_currency: Currency used as the base.
:class:`~rockefeller.currency.Currency` instance.
:param currency: Currency you want to know its exchange rate in relation
to ``base_currency.`` :class:`~rockefeller.currency.Currency` instance.
:param exchange_rate: Exchange rate as a string. :class:`str` instance.
"""
# you must implement this...
def get_exchange_rate(self, base_currency, currency):
"""Get exchange rate of a currency relatively to another one.
:param base_currency: Currency used as the base.
:class:`~rockefeller.currency.Currency` instance.
:param currency: Currency you want to know its exchange rate in relation
to ``base_currency.`` :class:`~rockefeller.currency.Currency` instance.
:return: Exchange rate as a string. :class:`str` instance.
"""
# you must implement this...
With that in place, you just have to tell rockefeller to start using that store like this:
rockefeller.set_exchange_rates_store(MyExchangeRateStore())
Contrib Stores
Since this library came out from a Google App Engine(GAE) project we shipped a
Currency
and ExchangeRates
stores. Each of these stores are going to
save the data in the datastore using the ndb
models
rockefeller.gae.models.Currency
and rockefeller.gae.models.ExchangeRate
.
Use this to plug the GAE currencies store:
rockefeller.set_currency_store(
rockefeller.gae.currency.GAECurrency(model=rockefeller.gae.models.Currency))
You can tell that you can even use your own GAE model, just make sure that it
has the get(code)
and support(currency)
class methods.
Use this to plug the GAE exchange rates store:
rockefeller.set_exchange_rates_store(
rockefeller.gae.exchange_rates.GAEExchangeRates(model=rockefeller.gae.models.ExchangeRate))
You can tell that you can even use your own GAE model, just make sure that it
has the add_exchange_rate(base_currency, currency, exchange_rate)
and
get_exchange_rate(base_currency, currency)
class methods.
Exchange Rates Service
This library comes with built-in support for openexchangerates service, that is, a service to automatically retrieve the current status of the exchange rates between different currencies.
Here's is simple example of what you have to do in order to be up to date with the exchange rates:
import logging
from django.conf import settings
from django import http
import rockefeller
from rockefeller.services import OpenExchangeRates, ServiceError
def cron_update_exchange_rates(req):
service = OpenExchangeRates(app_id=settings.EXCHANGE_RATES_APP_ID)
try:
exchange_rates = service.latest()
except ServiceError as e:
logging.exception(e)
else:
for exchange_rate in exchange_rates:
base_currency = rockefeller.Currency.get(exchange_rate.code_from)
currency = rockefeller.Currency.get(exchange_rate.code_to)
rockefeller.add_exchange_rate(base_currency, currency, exchange_rate.rate)
return http.HttpResponse('Exchange rates updated...')
If you're using App Engine, we provide an urlopener that uses App Engine urlfetch
service:
import logging
from django.conf import settings
from django import http
import rockefeller
from rockefeller.services import OpenExchangeRates, ServiceError
from rockefeller.openers import GaeOpener
def cron_update_exchange_rates(req):
OpenExchangeRates.url_opener = GaeOpener()
service = OpenExchangeRates(app_id=settings.EXCHANGE_RATES_APP_ID)
try:
exchange_rates = service.latest()
except ServiceError as e:
logging.exception(e)
else:
for exchange_rate in exchange_rates:
base_currency = rockefeller.Currency.get(exchange_rate.code_from)
currency = rockefeller.Currency.get(exchange_rate.code_to)
rockefeller.add_exchange_rate(base_currency, currency, exchange_rate.rate)
return http.HttpResponse('Exchange rates updated...')
Real World Example
This is real-world example of how we use this library. In our project, we have
a money.py
file that takes care of configuring the lib and initializing the
supported currencies.
from django.conf import settings
import rockefeller
import rockefeller.gae.models
import rockefeller.gae.currency
import rockefeller.gae.exchange_rates
rockefeller.set_currency_store(
rockefeller.gae.currency.GAECurrency(rockefeller.gae.models.Currency))
rockefeller.set_exchange_rates_store(
rockefeller.gae.exchange_rates.GAEExchangeRates(rockefeller.gae.models.ExchangeRate))
for code, currency in settings.SUPPORTED_CURRENCIES.iteritems():
rockefeller.Currency(**currency).support()
rockefeller.Money.indirection_currency = rockefeller.Currency.USD
Installation
The library has 0 dependencies outside of the standard library. In order to install it just download the source and run:
python setup.py install
Or you can install it directly from git using pip:
pip install -e git+http://github.com/anler/Rockefeller.git#egg=Rockefeller
Running tests
python setup.py test