moneyx - Precise Money Handling for Python
A lightweight, precise, and professional library for handling money in Python applications. moneyx solves precision, rounding, and representation issues that occur when using floating-point types like float for monetary calculations.
💰 Money Pattern
This library implements the Money Pattern as described by Martin Fowler in his book "Patterns of Enterprise Application Architecture" (2002). As Fowler notes:
"A large proportion of the computers in this world manipulate money, so it's always puzzled me that money isn't actually a first class data type in any mainstream programming language."
The Money Pattern creates a first-class representation of monetary values that handles currency, precision, and rounding issues. Learn more about the pattern at Martin Fowler's Money Pattern.
🚀 Installation
pip install moneyx
🔍 Key Features
- Absolute precision using
decimal.Decimalinternally - Extensive rounding modes (HALF_UP, HALF_DOWN, BANKERS, HALF_ODD, DOWN, UP, CEILING, FLOOR, HALF_TOWARDS_ZERO, HALF_AWAY_FROM_ZERO)
- Currency validation based on ISO 4217 standards
- Complete ISO 4217 support with proper symbols, decimal places, and country information
- Currency lookup by country or numeric code
- Currency conversion support
- Proportional allocation of amounts
- Tax calculation utilities
- Formatting with locale support
- Serialization to/from JSON and dictionaries
- Type annotations for better IDE support
- High performance with comprehensive benchmarks
- Thread-safe operations for concurrent environments
- 100% test coverage with property-based testing
📚 ISO 4217 Data Sources
This library uses official ISO 4217 currency data from:
- ISO 4217 Currency Codes - Official ISO information
- SIX Financial Information - Maintenance agency for ISO 4217 providing XML and XLS formats
📝 Usage Examples
Basic Usage
from moneyx import Money
# Create a monetary amount
price = Money("19.99", "USD")
# Basic arithmetic
tax = price.multiply(0.07) # 7% tax
total = price.add(tax)
print(f"Price: {price.format()}") # $19.99
print(f"Tax: {tax.format()}") # $1.40
print(f"Total: {total.format()}") # $21.39
Different Currencies
from moneyx import Money
# Create amounts in different currencies
usd = Money("100.00", "USD")
eur = Money("85.00", "EUR")
# Convert between currencies
eur_from_usd = usd.convert_to("EUR", rate=0.85)
print(f"{usd.format()} = {eur_from_usd.format_locale('de_DE')}") # $100.00 = 85,00 €
Currency Information and Lookup
from moneyx import Money
from moneyx.currency import Currency
# Get information about a currency
usd = Money("100.00", "USD")
print(f"Symbol: {usd.currency.symbol}") # $
print(f"Name: {usd.currency.name}") # US Dollar
print(f"Decimals: {usd.currency.decimals}") # 2
print(f"Countries: {', '.join(usd.currency.countries)}") # Lists all countries using USD
# Find currencies by country
swiss_currencies = Currency.get_by_country("SWITZERLAND")
for currency in swiss_currencies:
print(f"{currency.code}: {currency.name}") # CHF, CHE, CHW
# Find currency by numeric code
eur = Currency.get_by_number("978")
print(f"{eur.code}: {eur.name}") # EUR: Euro
Rounding Modes
from moneyx import Money, RoundingMode
# Classical rounding (HALF_UP)
standard = Money("2.5", "USD") # 2.50 -> rounds to 3 when needed
# Banker's rounding (round to even)
bankers = Money("2.5", "USD", rounding=RoundingMode.BANKERS) # 2.50 -> rounds to 2 when needed
bankers2 = Money("3.5", "USD", rounding=RoundingMode.BANKERS) # 3.50 -> rounds to 4 when needed
# Additional rounding modes
odd = Money("2.5", "USD", rounding=RoundingMode.HALF_ODD) # 2.50 -> rounds to 3 when needed
odd2 = Money("3.5", "USD", rounding=RoundingMode.HALF_ODD) # 3.50 -> rounds to 3 when needed
# Directional rounding
ceiling = Money("2.1", "USD", rounding=RoundingMode.CEILING) # Always rounds up
floor = Money("2.9", "USD", rounding=RoundingMode.FLOOR) # Always rounds down
towards_zero = Money("2.5", "USD", rounding=RoundingMode.HALF_TOWARDS_ZERO) # 2.5 -> 2
away_from_zero = Money("2.5", "USD", rounding=RoundingMode.HALF_AWAY_FROM_ZERO) # 2.5 -> 3
# Negative numbers
neg = Money("-2.5", "USD", rounding=RoundingMode.HALF_TOWARDS_ZERO) # -2.5 -> -2
neg2 = Money("-2.5", "USD", rounding=RoundingMode.HALF_AWAY_FROM_ZERO) # -2.5 -> -3
Allocation and Splitting
from moneyx import Money
# Divide a bill proportionally
bill = Money("100.00", "USD")
alice_part = 2 # Alice pays 2 parts
bob_part = 1 # Bob pays 1 part
shares = bill.allocate([alice_part, bob_part])
print(f"Alice pays: {shares[0].format()}") # $66.67
print(f"Bob pays: {shares[1].format()}") # $33.33
# Split a bill evenly
bill = Money("100.00", "USD")
equal_shares = bill.split_evenly(3)
for i, share in enumerate(equal_shares):
print(f"Person {i+1} pays: {share.format()}") # $33.33, $33.33, $33.34
Tax Calculations
from moneyx import Money
# Add tax to a price
price = Money("100.00", "USD")
with_tax = price.with_tax(10) # 10% tax
print(f"Price with tax: {with_tax.format()}") # $110.00
# Extract tax from a tax-inclusive amount
inclusive = Money("110.00", "USD")
tax_info = inclusive.extract_tax(10) # 10% tax
print(f"Base price: {tax_info['base'].format()}") # $100.00
print(f"Tax amount: {tax_info['tax'].format()}") # $10.00
Internationalization
from moneyx import Money
amount = Money("1234.56", "USD")
print(amount.format_locale("en_US")) # $1,234.56
print(amount.format_locale("es_ES")) # 1.234,56 $
print(amount.format_locale("de_DE")) # 1.234,56 $
Serialization
from moneyx import Money
import json
price = Money("99.99", "USD")
# To/from JSON
json_str = price.to_json()
restored = Money.from_json(json_str)
# To/from dictionary
data = price.to_dict()
restored = Money.from_dict(data)
Bulk Operations
from moneyx import Money
from moneyx.bulk import bulk_add, bulk_multiply, bulk_allocate
# Create multiple money objects
items = [
Money("10.50", "USD"),
Money("20.75", "USD"),
Money("5.99", "USD")
]
# Add all items together
total = bulk_add(items)
print(f"Total: {total}") # $37.24
# Apply different multipliers to each item
multipliers = [1.1, 1.05, 1.2]
adjusted = bulk_multiply(items, multipliers)
for item in adjusted:
print(item) # $11.55, $21.79, $7.19
# Allocate money in bulk by ratio
budget = Money("1000.00", "USD")
allocations = [1, 2, 3, 4] # Ratio 1:2:3:4
shares = bulk_allocate(budget, allocations)
for share in shares:
print(share) # $100.00, $200.00, $300.00, $400.00
🔒 Safe Money Handling
moneyx prevents common money-handling errors:
from moneyx import Money
from moneyx.exceptions import PrecisionError, InvalidCurrencyError
try:
# This will fail - USD allows max 2 decimals
Money("100.123", "USD")
except PrecisionError as e:
print(e) # The currency USD only allows 2 decimal places. Received: 100.123
try:
# This will fail - XYZ is not a valid currency
Money("100.00", "XYZ")
except InvalidCurrencyError as e:
print(e) # Unknown currency: XYZ
📊 Performance Benchmarks
moneyx is designed for high performance. Here are some key benchmarks:
| Operation | Performance |
|---|---|
| Money creation | ~670K ops/sec |
| Addition | ~660K ops/sec |
| Subtraction | ~665K ops/sec |
| Multiplication | ~450K ops/sec |
| Allocation | ~98K ops/sec |
| HALF_UP rounding | ~1.59M ops/sec |
| BANKERS rounding | ~1.75M ops/sec |
| HALF_ODD rounding | ~1.04M ops/sec |
| Bulk addition | ~448K ops/sec |
| Bulk multiplication | ~47K ops/sec |
| String formatting | ~4.3M ops/sec |
| Locale formatting | ~87K ops/sec |
Run benchmarks yourself with:
python -m pytest tests/test_benchmark.py --no-cov --benchmark-columns=Min,Max,Mean,Median,OPS
📖 License
MIT License - see LICENSE for details.
Metadata
Release files for moneyx 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| moneyx-0.1.1.tar.gz | 36.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| moneyx-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 59.8 kB
Release files / moneyx-0.1.1.tar.gz
| Download URL | moneyx-0.1.1.tar.gz |
|---|---|
| Size | 36.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
412359d038c56b8947a66caee1c881cd2ff17c098ffc28a29ace8180c8b6bdfd
|
|
BLAKE2b-256 checksum How to use checksums |
8f3f7576f63a9bcd3607f496ea8f997c31447036e3fac4b3f0633a36d2d6670b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.12.9
|
Release files / moneyx-0.1.1-py3-none-any.whl
| Download URL | moneyx-0.1.1-py3-none-any.whl |
|---|---|
| Size | 23.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
91ff44538555a8158813dc88545a9efc1ecb89c3445a13860efe8a3edf5d78d7
|
|
BLAKE2b-256 checksum How to use checksums |
f901d8fa328bcbe52c98866d7f8e09514d8f93cbd2468f8f78f8328b7a6e6f41
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.12.9
|