Python library for working with money and currencies, with Django integration.
- Type-safe money operations - Prevent errors like adding USD and EUR
- Immutable currency handling - Based on ISO-4217 standard
- Full type annotations - 100% typed with mypy strict mode
- Django model field - Store monetary values with proper currency tracking
- Conservative math operations - Explicit currency conversions, no implicit behavior
- Python: 3.10 or higher
- Django: 4.0.10 or 4.2.7
pip install git+https://github.com/makeleaps/python-money.git@v2.0.0Or add to your pyproject.toml:
dependencies = [
"python-money@git+https://github.com/makeleaps/python-money.git@v2.0.0"
]from money import Money, CURRENCY
# Create money values
usd_price = Money('19.99', CURRENCY['USD'])
usd_tax = Money('2.00', CURRENCY['USD'])
# Safe arithmetic
usd_total = usd_price + usd_tax # USD 21.99
# Prevents currency mistakes
eur = Money('10.00', CURRENCY['EUR'])
usd_price + eur # Raises CurrencyMismatchExceptionThis fork (rooted at poswald/python-money) takes a conservative approach to money operations:
- ✅ Explicit currency conversions only (no implicit conversion)
- ✅ No global state or default currencies
- ✅ Type-safe operations with full mypy coverage
- ✅ Modern tooling (uv, ruff, pytest, nox)
Not a drop-in replacement - If you're migrating from another fork, review the CHANGELOG.md for breaking changes, especially around imports and removed implicit conversion features.
This application contains several classes and functions that make dealing with money easier and less error-prone.
The Currency dataclass represents a type of currency with its code, ISO number, name, and countries:
Currency(code='BZD', numeric='084', name='Belize Dollar', countries=['BELIZE'])Use the CURRENCY constant to access all ISO-4217 currencies:
from money import CURRENCY
print(CURRENCY['GBP'].name) # 'Pound Sterling'
print(CURRENCY['USD'].code) # 'USD'The Money dataclass handles arithmetic with currency values safely. It wraps Python's Decimal type and prevents common mistakes like adding different currencies or multiplying two money values:
from money import Money, CURRENCY
# Create money values
usd = Money(amount=10.00, currency=CURRENCY['USD'])
print(usd) # USD 10.00
jpy = Money(amount=2000, currency=CURRENCY['JPY'])
print(jpy) # JPY 2000.00
# Safe operations
print(usd * 5) # USD 50.00
print(usd + usd) # USD 20.00
# Prevented operations
print(jpy * usd) # TypeError: can not multiply monetary quantities
print(jpy > usd) # TypeError: can not compare different currenciesUSD 0 == EUR 0→ False (different currencies)USD 0 == 0→ True (can compare to zero)USD 10 == EUR 10→ RaisesCurrencyMismatchException
You can only compare money with the same currency or with 0.
Allowed:
Money(10, 'USD') + Money(5, 'USD') # Money(15, 'USD')
Money(10, 'USD') - Money(5, 'USD') # Money(5, 'USD')
Money(10, 'USD') * 3 # Money(30, 'USD')
Money(10, 'USD') / 2 # Money(5, 'USD')Not allowed:
Money(10, 'USD') + Money(5, 'EUR') # CurrencyMismatchException
Money(10, 'USD') * Money(3, 'USD') # InvalidOperationException
Money(10, 'USD') / Money(2, 'USD') # InvalidOperationExceptionDividing two Money objects is ambiguous:
- Should
Money(9, 'USD') / Money(3, 'USD')returnMoney(3, 'USD')? - Or
Decimal('3')? - Or
Money(3, 'XXX')with undefined currency?
To keep operations explicit, this raises InvalidOperationException. If you need to divide amounts, use:
# Get a decimal ratio
Money(9, 'USD').amount / Money(3, 'USD').amount # Decimal('3')This makes your intent clear and prevents accidental currency errors.
Money behaves like Python's Decimal in boolean contexts:
bool(Money('0', 'USD')) # False
bool(Money('0.01', 'USD')) # True
bool(Money('1', 'USD')) # TrueTo check if a Money object exists, compare to None:
if amount is None:
amount = Money(0, 'USD')Optional Django support is included for convenience.
Add a currency field to your models:
from money.contrib.django.models.fields import MoneyField
class Product(models.Model):
price = MoneyField(default=0, max_digits=12, decimal_places=2)This creates two database columns:
price- stores the amount (e.g.,numeric(12,2))price_currency- stores the currency code (e.g.,varchar(3))
Usage:
product = Product.objects.get(id=123)
print(product.price) # USD 199.99When using fixtures, specify amount and currency separately:
{
"pk": 1,
"model": "myapp.product",
"fields": {
"price": "123.45",
"price_currency": "USD"
}
}The MoneyField uses PostgreSQL's numeric type to preserve user-entered precision. Values like 3, 3.0, and 3.000 are stored exactly as entered.
SQLite coerces NUMERIC values like 100.00 into integers, losing decimal precision. If you need to preserve user-entered precision, PostgreSQL is recommended over SQLite.
- Clone the repository:
git clone https://github.com/makeleaps/python-money.git
cd python-money- Install
uvand dependencies:
# Install uv (if not already installed)
# Install all dependencies including dev dependencies
uv sync --dev- Activate the environment:
source .venv/bin/activate # On Unix/macOS
# or
.venv\Scripts\activate # On WindowsLinting:
uv run ruff check .
uv run ruff format .Type checking:
uv run mypy .Tests:
uv run pytestInstall pre-commit hooks:
uv run pre-commit installOnce installed, pre-commit will automatically run ruff, mypy, and pytest before each commit.
See CHANGELOG.md for release history.