Precise and extensible library for handling money in Python
Project description
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file moneyx-0.1.1.tar.gz.
File metadata
- Download URL: moneyx-0.1.1.tar.gz
- Upload date:
- Size: 36.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
412359d038c56b8947a66caee1c881cd2ff17c098ffc28a29ace8180c8b6bdfd
|
|
| MD5 |
7cb4a0d6cd2434c2b714a71ff3fe50d7
|
|
| BLAKE2b-256 |
8f3f7576f63a9bcd3607f496ea8f997c31447036e3fac4b3f0633a36d2d6670b
|
File details
Details for the file moneyx-0.1.1-py3-none-any.whl.
File metadata
- Download URL: moneyx-0.1.1-py3-none-any.whl
- Upload date:
- Size: 23.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
91ff44538555a8158813dc88545a9efc1ecb89c3445a13860efe8a3edf5d78d7
|
|
| MD5 |
ffcdbbe8f77c2cf0466ed0b96ac6d8f7
|
|
| BLAKE2b-256 |
f901d8fa328bcbe52c98866d7f8e09514d8f93cbd2468f8f78f8328b7a6e6f41
|