Skip to main content

Precise and extensible library for handling money in Python

Project description

moneyx - Precise Money Handling for Python

Test Coverage: 100% License: MIT Python Versions

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.Decimal internally
  • 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:

📝 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


Download files

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

Source Distribution

moneyx-0.1.1.tar.gz (36.8 kB view details)

Uploaded Source

Built Distribution

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

moneyx-0.1.1-py3-none-any.whl (23.0 kB view details)

Uploaded Python 3

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

Hashes for moneyx-0.1.1.tar.gz
Algorithm Hash digest
SHA256 412359d038c56b8947a66caee1c881cd2ff17c098ffc28a29ace8180c8b6bdfd
MD5 7cb4a0d6cd2434c2b714a71ff3fe50d7
BLAKE2b-256 8f3f7576f63a9bcd3607f496ea8f997c31447036e3fac4b3f0633a36d2d6670b

See more details on using hashes here.

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

Hashes for moneyx-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 91ff44538555a8158813dc88545a9efc1ecb89c3445a13860efe8a3edf5d78d7
MD5 ffcdbbe8f77c2cf0466ed0b96ac6d8f7
BLAKE2b-256 f901d8fa328bcbe52c98866d7f8e09514d8f93cbd2468f8f78f8328b7a6e6f41

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