Skip to main content

Immutable money types for Python

Project description

immoney

CI Build Status Test coverage report
PyPI Package Python versions

Design goals

There are a few core design aspects of this library that each eliminate entire classes of bugs:

  • Exposed and internal data types are either immutable or faux immutable.
  • Invalid amounts of money cannot be represented. There is no such thing as 0.001 US dollars, and there is no such thing as negative money.
  • Builtin operations never implicitly lose precision.
  • Built from the ground-up with support for static type checking in mind. This means that bugs that attempt to mix currencies can be found by a static type checker.

Features

Safe division

In real life we cannot split the subunit of a currency, and so for our abstractions to safely reflect reality, we shouldn't be able to do that in code either. Therefore instead of defining division to return a value with precision loss, the implementation of division for Money returns a tuple of new instances with the value split up as even as possible.

>>> Money("0.11", SEK) / 3
(Money('0.04', SEK), Money('0.04', SEK), Money('0.03', SEK))

This method of division will always be safe, as it has the guaranteed property that the sum of the instances returned by the operation always equal the original numerator.

Subunit fractions

Sometimes we do need to represent fractions of monetary values that are smaller than the subunit of a currency, for instance as a partial result of a larger equation. For that purpose, this library exposes a SubunitFraction type. This type is used as return type for Money.__floordiv__.

>>> SEK(13) // 3
SubunitFraction('1300/3', SEK)

Because there is no guarantee that a SubunitFraction is a whole subunit (by definition ...), converting back to Money can only be done with precision loss.

>>> (SEK(13) // 3).round_money(Round.DOWN)
Money('4.33', SEK)

Overdraft

Again referring to real life, there is no such thing as negative money. Following in the same vein as for not allowing subunits to be split, the value of a Money instance cannot be negative. Instead, to represent for instance a negative balance on an account, this library exposes an Overdraft class that is used as return type of Money.__sub__ when the computed value would have been negative.

>>> balance = SEK(5)
>>> balance - SEK(4)
Money('1.00', SEK)
>>> balance - SEK(5)
Money('0.00', SEK)
>>> balance - SEK("6.50")
Overdraft('1.50', SEK)
>>> balance - SEK("6.50") + SEK("1.50")
Money('0.00', SEK)

Because negative values are encoded as its own type in this way, situations where negative values can result from arithmetic but aren't logically expected, such as for the price of an item in a store, can be discovered with a static type checker.

Type-safe comparison

Instances of Money do not support direct comparison with numeric scalar values. For convenience an exception is made for integer zero, which is always unambiguous.

Immediate and full instantiation

"2 dollars" is represented exactly the same as "2.00 dollars", in every aspect. This means that normalization of values happen at instantiation time.

Instantiating normalizes precision to the number of subunits of the instantiated currency.

>>> EUR(2)
Money('2.00', EUR)
>>> EUR("2.000")
Money('2.00', EUR)

Trying to instantiate with a value that would result in precision loss raises a runtime error.

>>> EUR("2.001")
Traceback (most recent call last):
  ...
immoney.errors.MoneyParseError: Cannot interpret value as Money of currency EUR ...

Instance cache

Since instances of Money and Currency are immutable it's safe to reuse existing instances instead of instantiating new ones. This happens transparently when instantiating a new Money instance and can lead to faster code and less consumed memory.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

immoney-0.0.3.tar.gz (16.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

immoney-0.0.3-py3-none-any.whl (13.8 kB view details)

Uploaded Python 3

File details

Details for the file immoney-0.0.3.tar.gz.

File metadata

  • Download URL: immoney-0.0.3.tar.gz
  • Upload date:
  • Size: 16.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.1 CPython/3.9.13

File hashes

Hashes for immoney-0.0.3.tar.gz
Algorithm Hash digest
SHA256 11c0ffd93e34272d19796c6fed4aa188e8393e88d3a7f80649be602f865ce4b9
MD5 92752166dc87337628cf4536d1fbcd04
BLAKE2b-256 ee979920a709cd92d986ad8a7babab034aa1fef3877dd288e5195fbfe870fc9a

See more details on using hashes here.

File details

Details for the file immoney-0.0.3-py3-none-any.whl.

File metadata

  • Download URL: immoney-0.0.3-py3-none-any.whl
  • Upload date:
  • Size: 13.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.1 CPython/3.9.13

File hashes

Hashes for immoney-0.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 fac1977ffda5ed1953e2c8d05b1087796f8445103a821e90268c488d46296888
MD5 f85552cdedfa67649c641b5a56ff0d7c
BLAKE2b-256 6ab79e02cbd0022adeb7f3107cfb0d305dd3e84710f1b5904299feb3fc4a9ef1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page