pypenny: Robust Currency Conversion Solution
Production-grade currency conversion library with smart error handling, encrypted caching, and fuzzy locale matching.
✨ Features
- 🎯 Unified API: Simple, intuitive top-level functions for all operations
- 💰 Smart Money Class: Immutable, hashable, and supports arithmetic operations
- 🧠 Smart Error Handling: Fuzzy locale matching auto-corrects typos (
em_US→en_US) - ⚙️ Flexible Configuration: Control cache behavior and restrict allowed currencies
- 🔒 Encrypted Cache: Fernet (AES-128) encryption with cross-platform storage
- 📊 Deduplication Logic: Only 1 record per day if exchange rate unchanged
- 🌍 180+ Currencies: Supports all ISO 4217 currencies via py-moneyed
- 🔄 Dual Exchange Strategies: Live network-based + encrypted local cache fallback
- 📦 Zero Config: Works out of the box with sensible defaults
🚀 Quick Start
Installation
# Using uv (recommended)
uv pip install pypenny
# Using pip
pip install pypenny
Basic Usage
import pypenny as pp
# Configure (optional, uses defaults if skipped)
pp.config(application_name="MyApp")
# Create money
money_usd = pp.Money('100', 'USD')
# Convert
money_egp = pp.convert(money_usd, 'EGP')
# Format (safe to print!)
print(pp.format(money_egp, locale='ar_EG'))
# Output: 4,752.00 ج.م.
Strict Configuration (Production)
import pypenny as pp
pp.config(
application_name="ProductionApp",
allow_cache_fallback=False, # Fail fast on network errors
allowed_currencies=['USD', 'EGP'], # Whitelist only
cache_max_records=10,
cache_retention_days=7
)
# This works
money_usd = pp.Money('100', 'USD')
# This raises CurrencyNotAllowedError with helpful message
try:
money_eur = pp.Money('100', 'EUR')
except pp.CurrencyNotAllowedError as e:
print(e)
# Error: Currency 'EUR' not allowed. Allowed: USD, EGP.
# Update config.allowed_currencies to include 'EUR'.
📚 Key Features
1. Arithmetic Operations
The Money class supports intuitive arithmetic operations:
m1 = pp.Money('100', 'USD')
m2 = pp.Money('50', 'USD')
# Arithmetic
total = m1 + m2 # 150.00 USD
diff = m1 - m2 # 50.00 USD
doubled = m1 * 2 # 200.00 USD
halved = m1 / 2 # 50.00 USD
# Comparison
is_greater = m1 > m2 # True
2. Fuzzy Locale Matching
Automatically corrects common locale mistakes:
pp.format(money, locale='EN_us') # → Normalized to 'en_US'
pp.format(money, locale='US_EN') # → Swapped to 'en_US'
pp.format(money, locale='em_US') # → Typo corrected to 'en_US'
pp.format(money, locale='US') # → Alias resolved to 'en_US'
3. Encrypted Cache with Deduplication
# Cache automatically stores exchange rates
money_converted = pp.convert(money_usd, 'EGP')
# Get cache statistics
stats = pp.get_cache_stats()
print(stats['total_records']) # Number of cached rates
print(stats['cache_file']) # Path to encrypted cache file
# Cleanup old records
pp.cleanup_cache()
4. Exchange Strategies
# LIVE: Always fetch from network
converted = pp.convert(money, 'EGP', strategy='live')
# CACHED: Use local cache only
converted = pp.convert(money, 'EGP', strategy='cached')
# AUTO: Try live, fallback to cache (default)
converted = pp.convert(money, 'EGP', strategy='auto')
🔧 Configuration Options
pp.config(
# Required
application_name: str, # For cache directory naming
# Cache behavior
allow_cache_fallback: bool = True, # Fallback to cache on network failure
cache_max_records: int = 7, # Max records per currency pair
cache_retention_days: int = 3, # Days to retain cached records
cache_file_path: str = None, # Custom cache path (None = platformdirs)
# Currency restrictions
allowed_currencies: List[str] = None, # Whitelist (None = all allowed)
# Exchange settings
default_exchange_strategy: str = 'auto', # 'live', 'cached', 'auto'
default_locale: str = 'en_US',
# API settings
api_key: str = None,
api_timeout: int = 5,
api_max_retries: int = 3,
)
🧪 Testing
# Install dev dependencies
uv pip install -e ".[dev]"
# Run tests
pytest
# Run with coverage
pytest --cov=. --cov-report=html
# Run stress tests
pytest tests/test_stress.py -v
🏗️ Architecture
┌─────────────┐
│ pypenny │ ← Unified Package API
└──────┬──────┘
│
┌──────▼────────┐
│CurrencyManager│
└──────┬────────┘
│
┌────┴────┬──────────┬────────────┐
▼ ▼ ▼ ▼
┌────────┐ ┌──────┐ ┌─────────┐ ┌──────────┐
│ Config │ │Locale│ │Exchange │ │Encryption│
│ │ │Match │ │ Cache │ │ Utils │
236: └────────┘ └──────┘ └─────────┘ └──────────┘
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🙏 Acknowledgments
- Built on py-moneyed for robust currency handling
- Uses Babel for locale-aware formatting
- Encryption via cryptography
- Cross-platform paths via platformdirs
📞 Support
Made with ❤️ for developers who need reliable currency handling
Metadata
Release files for pypenny 0.1.0.post1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pypenny-0.1.0.post1.tar.gz | 37.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pypenny-0.1.0.post1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 67.1 kB
Release files / pypenny-0.1.0.post1.tar.gz
| Download URL | pypenny-0.1.0.post1.tar.gz |
|---|---|
| Size | 37.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
924488b97871ddff9ebae5d70e082c56d683d8ccdffdd1022754af845da51133
|
|
BLAKE2b-256 checksum How to use checksums |
d9d363569835a4518ca50c1eefa1b0080e49212498112632104704a7d8991795
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.8.17
|
Release files / pypenny-0.1.0.post1-py3-none-any.whl
| Download URL | pypenny-0.1.0.post1-py3-none-any.whl |
|---|---|
| Size | 29.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c82facfedfefabc217aa1feb3158a27b0aea61e4811cf6043fa3e148c93fde3c
|
|
BLAKE2b-256 checksum How to use checksums |
3159952599de0964e9a1131755ccdce397a7199edbc4eece0daf6e91c9e77f6d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.8.17
|