Skip to main content

pypenny: Robust Currency Conversion Solution

Python 3.8+ License: MIT Code style: black

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

📞 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)

Source distribution for pypenny 0.1.0.post1
File Size Uploaded
pypenny-0.1.0.post1.tar.gz 37.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pypenny 0.1.0.post1
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

0.1.0.post1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page