Python money class with optional CLDR-backed locale-aware formatting and an extensible currency exchange solution.

Related tags

E-commerce money
Overview

Python Money

Money class with optional CLDR-backed locale-aware formatting and an extensible currency exchange solution.

This is version 1.4.0-dev.

Development: https://github.com/carlospalol/money
Latest release: https://pypi.python.org/pypi/money/

This package is compatible with Python 2.7, 3.4, 3.5, but there are important Differences between Python versions. All code examples use Python 3.5.

Contents

Installation

Install the latest release with:

pip install money

For locale-aware formatting, also install the latest version of Babel (2.2 or 2.3 required):

pip install babel

Usage

>>> from money import Money
>>> m = Money(amount='2.22', currency='EUR')
>>> m
EUR 2.22

amount can be any valid value in decimal.Decimal(value) and currency should be a three-letter currency code. Money objects are immutable by convention and hashable. Once created, you can use read-only properties amount (decimal.Decimal) and currency (str) to access its internal components:

>>> m = Money(2, 'USD')
>>> m.amount
Decimal('2')
>>> m.currency
'USD'

Money emulates a numeric type and you can apply most arithmetic and comparison operators between money objects, as well as addition, subtraction, and division with integers (int) and decimal numbers (decimal.Decimal):

>>> m = Money('2.22', 'EUR')
>>> m / 2
EUR 1.11
>>> m + Money('7.77', 'EUR')
EUR 9.99

More formally, with AAA and BBB being different currencies:

  Operator Money AAA Money BBB int, Decimal
Money AAA +, - Money N/A Money
* N/A N/A Money
/, // Decimal N/A Money
>, >= <, <= Compares amount. N/A N/A
== False False

Arithmetic operations with floats are not directly supported. If you need to operate with floats, you must first convert the float to a Decimal, or the Money object to a float (i.e. float(m)). Please be aware of the issues and limitations of floating point arithmetics.

Currency presets

If you use fixed currencies in your code, you may find convenient to create currency-preset Money subclasses:

class EUR(Money):
    def __init__(self, amount='0'):
        super().__init__(amount=amount, currency='EUR')

price = EUR('9.99')

Formatting

Money objects are printed by default with en_US formatting and the currency code.

>>> m = Money('1234.567', 'EUR')
>>> str(m)
'EUR 1,234.57'

Use format(locale=LC_NUMERIC, pattern=None, currency_digits=True, format_type='standard') for locale-aware formatting with currency expansion. format() relies on babel.numbers.format_currency(), and requires Babel to be installed.

>>> m = Money('1234.567', 'USD')
>>> m.format('en_US')
'$1,234.57'
>>> m.format('es_ES')
'1.234,57\xa0$'

The character \xa0 is an unicode non-breaking space. If no locale is passed, Babel will use your system's locale. You can also provide a specific pattern to format():

>>> m = Money('-1234.567', 'USD')
>>> # Regular US format:
>>> m.format('en_US', '¤#,##0.00')
'-$1,234.57'
>>> # Custom negative format:
>>> m.format('en_US', '¤#,##0.00;<¤#,##0.00>')
'<$1,234.57>'
>>> # Spanish format, full currency name:
>>> m.format('es_ES', '#,##0.00 ¤¤¤')
'-1.234,57 dólares estadounidenses'
>>> # Same as above, but rounding (overriding currency natural format):
>>> m.format('es_ES', '#0 ¤¤¤', currency_digits=False)
'-1235 dólares estadounidenses'

For more details on formatting see Babel docs on currency formatting. To learn more about the formatting pattern syntax check out Unicode TR35.

Currency exchange

Currency exchange works by "installing" a backend class that implements the abstract base class (abc) money.exchange.BackendBase. Its API is exposed through money.xrates, along with setup functions xrates.install(pythonpath), xrates.uninstall(), and xrates.backend_name.

A simple proof-of-concept backend money.exchange.SimpleBackend is included:

from decimal import Decimal
from money import Money, xrates

xrates.install('money.exchange.SimpleBackend')
xrates.base = 'USD'
xrates.setrate('AAA', Decimal('2'))
xrates.setrate('BBB', Decimal('8'))

a = Money(1, 'AAA')
b = Money(1, 'BBB')

assert a.to('BBB') == Money('4', 'BBB')
assert b.to('AAA') == Money('0.25', 'AAA')
assert a + b.to('AAA') == Money('1.25', 'AAA')

XMoney

You can use money.XMoney (a subclass of Money), for automatic currency conversion while adding, subtracting, and dividing money objects (+, +=, -, -=, /, //). This is useful when aggregating lots of money objects with heterogeneous currencies. The currency of the leftmost object has priority.

from money import XMoney

# Register backend and rates as above...

a = XMoney(1, 'AAA')
b = XMoney(1, 'BBB')

assert sum([a, b]) == XMoney('1.25', 'AAA')

Exceptions

Found in money.exceptions.

MoneyException(Exception)
Base class for all exceptions.
CurrencyMismatch(MoneyException, ValueError)
Thrown when mixing different currencies, e.g. Money(2, 'EUR') + Money(2, 'USD'). Money objects must be converted first to the same currency, or XMoney could be used for automatic conversion.
InvalidOperandType(MoneyException, TypeError)
Thrown when attempting invalid operations, e.g. multiplication between money objects.
ExchangeError(MoneyException)
Base class for exchange exceptions.
ExchangeBackendNotInstalled(ExchangeError)
Thrown if a conversion is attempted, but there is no backend available.
ExchangeRateNotFound(ExchangeError)
The installed backend failed to provide a suitable exchange rate between the origin and target currencies.

Hierarchy

  • MoneyException
    • CurrencyMismatch
    • InvalidOperandType
    • ExchangeError
      • ExchangeBackendNotInstalled
      • ExchangeRateNotFound

Differences between Python versions

Expression Python 2.x Python 3.x
round(Money('2.5', 'EUR')) Returns 3.0, a float rounded amount away from zero. Returns EUR 2, a Money object with rounded amount to the nearest even.
Money('0', 'EUR').amount < '0' Returns True. This is the weird but expected behaviour in Python 2.x when comparing Decimal objects with non-numerical objects (Note the '0' is a string). See note in docs. TypeError: unorderable types: decimal.Decimal() > str()

Design decisions

There are several design decisions in money that differ from currently available money class implementations:

Localization

Do not keep any kind of locale conventions database inside this package. Locale conventions are extensive and change over time; keeping track of them is a project of its own. There is already such a project and database (the Unicode Common Locale Data Repository), and an excellent python API for it: Babel.

Currency

There is no need for a currency class. A currency is fully identified by its ISO 4217 code, and localization or exchange rates data are expected to be centralized as databases/services because of their changing nature.

Also:

  • Modulo operator (%): do not override to mean "percentage".
  • Numeric type: you can mix numbers and money in binary operations, and objects evaluate to False if their amount is zero.
  • Global default currency: subclassing is a safer solution.

Contributions

Contributions are welcome. You can use the regular github mechanisms.

To test your changes you will need tox and python 2.7, 3.4, and 3.5. Simply cd to the package root (by setup.py) and run tox.

License

money is released under the MIT license, which can be found in the file LICENSE.

Comments
  • Make Money hashable?

    Make Money hashable?

    Although it could be both a question and a proposal at a time, is there any reason why Money is unhashable?

    >>> m = money.Money(123, 'KRW')
    >>> hash(m)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: unhashable type: 'Money'
    

    Money.__hash__ is set to None, and it disallows Money to be hashed.

    decimal.Decimal is also hashable:

    >>> d = decimal.Decimal(123)
    >>> hash(d)
    123
    

    So I think __hash__ could be easily implemented e.g.:

    def __hash__(self):
        return hash((self.amount, self.currency))
    
    opened by dahlia 6
  • Support for SQLAlchemy composite columns

    Support for SQLAlchemy composite columns

    Implementing __composite_values__() method is pretty trivial, but it makes Money/XMoney possible to work fantastically with SQLAlchemy through composite columns:

    class Product(Base):
    
        amount = Column(Numeric, nullable=False)
        currency = Column(String(3), nullable=False)
        price = composite(Money, amount, currency)
    

    It even doesn’t need to depend on SQLAlchemy. :smile:

    Also, should it be documented in README.rst?

    opened by dahlia 5
  • Collaboration

    Collaboration

    Hi,

    Thanks for this wonderful package. Did you know there is another money package for python: https://github.com/limist/py-moneyed

    I like your approach more, integration with Babel is a very good thing. I'm planning to integrate your package to SQLAlchemy-Utils by providing Money type for declarative models.

    opened by kvesteri 5
  • 0 dollars is the same as 0 euros.. or anything.

    0 dollars is the same as 0 euros.. or anything.

    Allow comparisons between Money and 0 (integer, decimal, or otherwise). This shortens code from:

    this._ZERO = Money(0, this.currency)
    if x > this._ZERO:
    ...
    

    to:

    if x > 0:
    ...
    
    opened by g-- 4
  • Formatting is broken

    Formatting is broken

    Using Python 2.7.11, money 1.2.1, Babel 2.3.3:

    >>> from money import Money
    >>> Money('10', 'USD')
    USD 10
    >>> Money('10', 'USD').format()
    Traceback (most recent call last):
      File "<console>", line 1, in <module>
      File "/home/chris/proj/move/carts/eggs/money-1.2.1-py2.7.egg/money/money.py", line 249, in format
        pattern = babel.numbers.parse_pattern(pattern)
      File "/home/chris/proj/move/carts/eggs/Babel-2.3.3-py2.7.egg/babel/numbers.py", line 491, in parse_pattern
        if ';' in pattern:
    TypeError: argument of type 'NoneType' is not iterable
    >>> Money('10', 'USD').format('en_US')
    Traceback (most recent call last):
      File "<console>", line 1, in <module>
      File "/home/chris/proj/move/carts/eggs/money-1.2.1-py2.7.egg/money/money.py", line 249, in format
        pattern = babel.numbers.parse_pattern(pattern)
      File "/home/chris/proj/move/carts/eggs/Babel-2.3.3-py2.7.egg/babel/numbers.py", line 491, in parse_pattern
        if ';' in pattern:
    TypeError: argument of type 'NoneType' is not iterable
    >>> 
    
    opened by chrisrossi 4
  • Remove ISO compliance check for currency

    Remove ISO compliance check for currency

    I really like your library so far. Thank you for writing it!

    There is however one problem, as I'm working with cryptocurrencies, that are not always compliant to the ISO currency standard. Can you remove the ISO check when creating currencies? Or can you make it configurable to be able to switch it off?

    opened by jbieler 3
  • Improvement to money.exchange.ExchangeRates.install API.

    Improvement to money.exchange.ExchangeRates.install API.

    Extend money.exchange.ExchangeRates.install to accept a class or instance in addition to the currently supported dotted Python path string. This makes configuration at runtime a little easier in some cases.

    opened by chrisrossi 3
  • Money does automatic conversion of floats, which is extremely unhelpful

    Money does automatic conversion of floats, which is extremely unhelpful

    Compare:

    >>> from decimal import Decimal
    >>> Decimal("2.3") * 1.2
    TypeError: unsupported operand type(s) for *: 'decimal.Decimal' and 'float'
    

    There are extremely good reasons for this behaviour, and is one of the primary reasons that you would choose to use Decimal to represent money. A decimal library must never do a lossy conversion, and I use the Python Decimal module precisely because I can rely on it to do this correctly, and throw an exception if I ever make the mistake (and it's always a mistake) of mixing floats into my decimal calculations.

    With money, on the other hand:

    >>> Money(amount='2.3', currency='GBP') * 1.2
    >>> GBP 2.759999999999999897859481734
    

    This behaviour is extremely surprising, and should instead raise an exception, the same one as above. In my opinion, with the current behaviour, this library is seriously flawed.

    opened by spookylukey 3
  • Subclassing `money.Money` with operators.

    Subclassing `money.Money` with operators.

    from money import Money
    
    m = Money('0', 'BTC') + Money('0', 'BTC')
    # BTC 0
    
    class BTC(Money):
        def __init__(self, amount=0):
            super().__init__(amount=amount, currency='BTC')
    
    m = BTC('0') + BTC('0')
    

    crash

    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File ".../site-packages/money/money.py", line 119, in __add__
        return self.__class__(amount, self._currency)
    TypeError: __init__() takes from 1 to 2 positional arguments but 3 were given
    

    I would like to get the subclass instances to implement arithmetic, equality, and comparison operators.

    Do I have to implement all of the special methods and delegate a call using super, or is there an artificial limit to the current implementation?

    opened by allonhadaya 2
  • Import error

    Import error

    Hi, I'm trying to use money into a GAE app.

    For testing purposes, I've tried into the console and keep raising the same error. Here's a simple print from the console:

    drwxr-xr-x   7 me  admin   238B 28 Mai 12:05 ./
    drwxr-xr-x@ 14 me  admin   476B 28 Mai 12:02 ../
    -rw-r--r--@  1 me  admin   6,0K 28 Mai 12:05 .DS_Store
    -rw-r--r--@  1 me  admin     0B 28 Mai 10:57 __init__.py
    drwxr-xr-x@ 19 me  admin   646B 28 Mai 12:06 babel/
    drwxr-xr-x@  9 me  admin   306B 28 Mai 11:47 money/
    drwxr-xr-x@  9 me  admin   306B 28 Mai 11:14 moneyed/
    mymac:libs $ python
    Python 2.7.9 (default, Apr  7 2015, 07:58:25) 
    [GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import babel
    >>> import money
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "money/__init__.py", line 5, in <module>
        from .money import Money, XMoney
      File "money/money.py", line 33
        "Decimal(): '{}'".format(amount)) from None
                                             ^
    SyntaxError: invalid syntax
    >>> 
    

    Any guidance?

    opened by rvogel 2
  • Exceptions should inherit from same base class

    Exceptions should inherit from same base class

    The exception classes in this package currently don't all inherit from the same base class. This makes it difficult to "do some money calculations" and stop whenever an error occurs. For example:

    from decimal import Decimal
    from money import xrates, Money, XMoney
    xrates.install('money.exchange.SimpleBackend')
    xrates.base = 'GBP'
    xrates.setrate('EUR', Decimal('2'))
    
    # Raises money.exceptions.ExchangeRateNotFound:
    price1 = XMoney('3.00', 'GBP') + Money('50.00', 'AUD')
    
    # Raises money.exceptions.CurrencyMismatch:
    price2 = Money('3.00', 'GBP') + XMoney('50.00', 'AUD')
    

    This means as a developer I basically have to inspect the money code to know what exceptions can be raised. I would like to write:

    try:
        price1 = XMoney('3.00', 'GBP') + Money('50.00', 'AUD')
        price2 = Money('3.00', 'GBP') + XMoney('50.00', 'AUD')
    except (all money exceptions to catch here):
        print("Something went wrong")
    

    but I can't because I'd need to know what all those exceptions are. By introducing a base class we can catch and handle everything:

    try:
        price1 = XMoney('3.00', 'GBP') + Money('50.00', 'AUD')
        price2 = Money('3.00', 'GBP') + XMoney('50.00', 'AUD')
    except MoneyException:
        print("Something went wrong")
    

    Also, please don't consider these several issue reports as a bad thing; I'm considering to use this package and I want to make sure we don't run into unexpected issues down the line.

    opened by svisser 2
  • Add statistics support

    Add statistics support

    I am trying to combine money with Python's statistics module.

    When I am e.g. using the mean function on a list of Money objects, I am getting the error

    Exception(TypeError("can't convert type 'Money' to numerator/denominator"))
    

    At the moment, I am subclassing Money:

    class EUR(Money, Decimal):
        def __init__(self, amount="0", currency="EUR"):
            super().__init__(amount=amount, currency=currency)
    
        def as_integer_ratio(self):
            """Express a Money instance in the form n / d"""
    
            return self.amount.as_integer_ratio()
    

    This still leaves me with statistics returning Decimal objects but maybe a fix for this may be found later.

    I would appreciate if you could add an as_integer_ratio method to Money as displayed above. If subclassing Money from Decimal is not leading to issues, it may also be worth a thought.

    opened by manns 0
  • Passing a money instance to create a copy/clone?

    Passing a money instance to create a copy/clone?

    Both of the last lines here fail....

    from money import Money
    
    AUD='AUD'
    
    a=Money(10,AUD)
    b=Money(20,AUD)
    c=a+b
    print(a)
    print(b)
    print(c)
    d=Money(c)
    d=Money(c,AUD)
    

    would it be worth adding a check in init to see if isinstance(amount,Money) and if the currency matches, use the amount._amount value from the passed in arg?

    I guess its bad form to create another instance for the same value rather than returning c.

    opened by korg 0
  • Added <= <, >, >= but not == to XMoney.

    Added <= <, >, >= but not == to XMoney.

    Operations convert to the same currency as the left operand. == is not defined because that would either make the class unhashable, or make hashing an expensive operation.

    Since we can't reasonably be both hashable and have an == operator that converts types. An alternative approach would be to create subclass of XMoney that isn't hashable and includes all the comparison operators.

    opened by rbiro 1
  •  amount = self._amount + other TypeError: unsupported operand type(s) for +: 'decimal.Decimal' and 'float'

    amount = self._amount + other TypeError: unsupported operand type(s) for +: 'decimal.Decimal' and 'float'

    I have my very easy code:

    #!/usr/bin/env python
    # -*- coding: UTF-8 -*-
    
    from money import Money								
    x = Money(amount='0.01', currency='USD')
    
    words = 'alphabet'
    
    for i in words:
    	x += 0.01
    
    print (x)
    

    But it just won't run:

    Traceback (most recent call last):
      File "test.py", line 10, in <module>
        x += 0.01
      File "C:\Program Files\Python36\lib\site-packages\money\money.py", line 118, in __add__
        amount = self._amount + other
    TypeError: unsupported operand type(s) for +: 'decimal.Decimal' and 'float'
    

    I believe += is the problem. Any way out?

    opened by francesco1119 0
  • Currency pair classes

    Currency pair classes

    I have an use-case where I'm comparing different exchange rates. From Wikipedia: A currency pair is EUR/USD. Here, EUR is the base currency and USD is the quote currency. The quotation EUR/USD 1.2500 means that one euro is exchanged for 1.2500 US dollars.

    I'd like to write something like

    # Convert here 1 EUR for 1 USD
    exchange1 = CurrencyPair(base = 'EUR', quote = 'USD', rate = '1.25')
    
    # Convert here 1 EUR for 2 USD
    exchange2 = CurrencyPair(base = 'EUR', quote = 'USD', rate = '2.50')
    
    euro = Money(amount='1.00', currency='EUR')
    dollar1 = exchange1 * euro   # dollar1 is now equal to Money(amount='1.25', currency='USD')
    dollar2 = exchange2 * euro   # dollar2 is now equal to Money(amount='2.50', currency='USD')
    

    I made this quickly by duplicating the Money class and changing some of it's multiplication operations.

    Is this something you'd like to support? If so, I can make a proper pull request.

    opened by jeroenmeulenaar 5
Owner
Carlos Palol
Carlos Palol
Foreign exchange rates, Bitcoin price index and currency conversion using ratesapi.io

forex-python Forex Python is a Free Foreign exchange rates and currency conversion. Note: Install latest forex-python==1.1 to avoid RatesNotAvailableE

MicroPyramid 540 Jan 5, 2023
Currency Conversion in Python

CurrencyConversion connect to an API to do currency conversions, save as json text or screen output exchangeratesAPI.py -h Exchange Rates via 'api.cur

soup-works 1 Jan 29, 2022
Portfolio and E-commerce site built on Python-Django and Stripe checkout

StripeMe Introduction Stripe Me is an e-commerce and portfolio website offering communication services, including web-development, graphic design and

null 3 Jul 5, 2022
A modular, high performance, headless e-commerce platform built with Python, GraphQL, Django, and React.

Saleor Commerce Customer-centric e-commerce on a modern stack A headless, GraphQL-first e-commerce platform delivering ultra-fast, dynamic, personaliz

Mirumee Labs 17.7k Dec 31, 2022
Fully functional ecommerce website with user and guest checkout capabilities and Paypal payment integration.

ecommerce_website Fully functional ecommerce website with user and guest checkout capabilities and Paypal payment integration. pip install django pyth

null 2 Jan 5, 2022
An Unofficial Alipay API for Python

An Unofficial Alipay API for Python Overview An Unofficial Alipay API for Python, It Contain these API: Generate direct payment url Generate partner t

Eric Lo 321 Dec 24, 2022
A web application to search for input products across several supermarkets' e-commerce to return price, detail of products running on Python.

Price Checker A web application to search for input products across several supermarkets' e-commerce to return price, detail of products. Requirements

null 3 Jun 28, 2022
Storefront - An E-commerce StoreFront Application Built With Python

An E-commerce StoreFront Application A very robust storefront project. This is a

Fachii Felix Zasha 1 Apr 4, 2022
PVE with tcaledger app for payments and simulation of payment requests

tcaledger PVE with tcaledger app for payments and simulation of payment requests. The purpose of this API is to empower users to accept cryptocurrenci

null 3 Jan 29, 2022
An eBay-like e-commerce auction site that will allow users to post auction listings, place bids on listings, comment on those listings, and add listings to a watchlist.

e-commerce-auction-site This repository is my solution to Commerce project of CS50’s Web Programming with Python and JavaScript course by Harvard. ??

null 3 Sep 3, 2022
Ecommerce app using Django, Rest API and ElasticSearch

e-commerce-app Ecommerce app using Django, Rest API, Docker and ElasticSearch Sort pipfile pipfile-sort Runserver with Werkzeug (django-extensions) .

Nhat Tai NGUYEN 1 Jan 31, 2022
Display money format and its filthy currencies, for all money lovers out there.

Python Currencies Display money format and its filthy currencies, for all money lovers out there. Installation currencies is available on PyPi http://

Alireza Savand 64 Dec 28, 2022
Currency And Gold Prices - Currency And Gold Prices For Python

Currency_And_Gold_Prices Photos from the app New Update Show range Change better

Ali HemmatNia 4 Sep 19, 2022
Fully Automated YouTube Channel ▶️with Added Extra Features.

Fully Automated Youtube Channel ▒█▀▀█ █▀▀█ ▀▀█▀▀ ▀▀█▀▀ █░░█ █▀▀▄ █▀▀ █▀▀█ ▒█▀▀▄ █░░█ ░░█░░ ░▒█░░ █░░█ █▀▀▄ █▀▀ █▄▄▀ ▒█▄▄█ ▀▀▀▀ ░░▀░░ ░▒█░░ ░▀▀▀ ▀▀▀░

sam-sepiol 249 Jan 2, 2023
Skepticoin is a peer-to-peer digital currency that enables you to send money online

What is Skepticoin? Skepticoin is a peer-to-peer digital currency that enables you to send money online. It's also the central community of people who

null 64 Aug 6, 2022
This python code will get requests from SET (The Stock Exchange of Thailand) a previously-close stock price and return it in Thai Baht currency using beautiful soup 4 HTML scrapper.

This python code will get requests from SET (The Stock Exchange of Thailand) a previously-close stock price and return it in Thai Baht currency using beautiful soup 4 HTML scrapper.

Andre 1 Oct 24, 2022
🧮A simple calculator written in python allows you to make simple calculations, write charts, calculate the dates, and exchange currency.

Calculator ?? A simple calculator written in python allows you to make simple calculations, write charts, calculate the dates, and exchange currency.

Jan Kupczyk 1 Jan 15, 2022
Foreign exchange rates, Bitcoin price index and currency conversion using ratesapi.io

forex-python Forex Python is a Free Foreign exchange rates and currency conversion. Note: Install latest forex-python==1.1 to avoid RatesNotAvailableE

MicroPyramid 540 Jan 5, 2023
Python disk-backed cache (Django-compatible). Faster than Redis and Memcached. Pure-Python.

DiskCache is an Apache2 licensed disk and file backed cache library, written in pure-Python, and compatible with Django.

Grant Jenks 1.7k Jan 5, 2023
Python Flask API service, backed by DynamoDB, running on AWS Lambda using the traditional Serverless Framework.

Serverless Framework Python Flask API service backed by DynamoDB on AWS Python Flask API service, backed by DynamoDB, running on AWS Lambda using the

Andreu Jové 0 Apr 17, 2022