Metadata-Version: 2.4
Name: flask-unirate
Version: 0.1.0
Summary: Flask extension for the UniRate currency-exchange API — Jinja filters, optional Flask-Caching integration, and current_app.unirate access.
Project-URL: Homepage, https://github.com/UniRate-API/flask-unirate
Project-URL: Repository, https://github.com/UniRate-API/flask-unirate
Project-URL: Issues, https://github.com/UniRate-API/flask-unirate/issues
Project-URL: Provider, https://unirateapi.com
Author: Unirate Team
License: MIT
License-File: LICENSE
Keywords: currency,exchange-rates,fintech,flask,forex,jinja,money,unirate
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: Flask
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Requires-Dist: flask<4.0,>=2.0
Requires-Dist: unirate-api<2.0,>=1.0
Provides-Extra: caching
Requires-Dist: flask-caching<3.0,>=2.0; extra == 'caching'
Description-Content-Type: text/markdown

# flask-unirate

[![PyPI](https://img.shields.io/pypi/v/flask-unirate.svg)](https://pypi.org/project/flask-unirate/)
[![Python](https://img.shields.io/pypi/pyversions/flask-unirate.svg)](https://pypi.org/project/flask-unirate/)
[![License](https://img.shields.io/pypi/l/flask-unirate.svg)](https://github.com/UniRate-API/flask-unirate/blob/main/LICENSE)

Flask extension for the [UniRate](https://unirateapi.com) currency-exchange API:

- **Drop-in `UniRate(app)` extension** — follows the standard
  `init_app` factory pattern, registers itself on
  `app.extensions["unirate"]`.
- **Jinja filters** — `{{ amount|to_currency('EUR') }}`,
  `{{ amount|convert_currency('USD', 'JPY') }}`,
  `{{ price|format_money('USD') }}`, plus `to_usd` / `to_eur` / `to_gbp`
  shortcuts.
- **Optional Flask-Caching integration** — set
  `UNIRATE_CACHE_TIMEOUT` and the extension caches latest-rate /
  supported-currency lookups through whatever Flask-Caching backend you
  already have.
- Wraps the official [`unirate-api`](https://pypi.org/project/unirate-api/)
  Python client; full method surface is reachable through
  `unirate.client` if you need historical rates / VAT / time series.

UniRate covers 593+ fiat, crypto, and commodity codes. Latest rates and
conversion are on the free tier; historical endpoints
(`get_historical_rate`, `convert_historical`) require Pro.

## Install

```bash
pip install flask-unirate
```

With Flask-Caching support:

```bash
pip install "flask-unirate[caching]"
```

## Quick start

```python
import os

from flask import Flask, render_template_string

from flask_unirate import UniRate

app = Flask(__name__)
app.config["UNIRATE_API_KEY"] = os.environ["UNIRATE_API_KEY"]
unirate = UniRate(app)


@app.route("/rate/<base>/<quote>")
def rate(base: str, quote: str):
    return {"rate": unirate.get_rate(base, quote)}


@app.route("/checkout/<int:amount_usd>")
def checkout(amount_usd: int):
    return render_template_string(
        """
        <p>USD: {{ amount|format_money('USD') }}</p>
        <p>EUR: {{ amount|to_eur|format_money('EUR') }}</p>
        <p>JPY: {{ amount|to_currency('JPY')|format_money('JPY', decimals=0) }}</p>
        """,
        amount=amount_usd,
    )
```

## Configuration

| Key | Default | Notes |
|---|---|---|
| `UNIRATE_API_KEY` | — | Required. Falls back to the `UNIRATE_API_KEY` env var. |
| `UNIRATE_TIMEOUT` | 30 (s) | HTTP timeout passed to `UnirateClient`. |
| `UNIRATE_BASE_URL` | `https://api.unirateapi.com` | Override only if you proxy the API. |
| `UNIRATE_DEFAULT_BASE_CURRENCY` | `USD` | Default base for the `to_currency` Jinja filter. |
| `UNIRATE_CACHE_TIMEOUT` | unset | If set *and* Flask-Caching is initialised on the app, latest-rate / supported-currency lookups are cached for this many seconds. |

## Factory pattern

```python
from flask import Flask
from flask_unirate import UniRate

unirate = UniRate()


def create_app() -> Flask:
    app = Flask(__name__)
    app.config.from_pyfile("config.py")
    unirate.init_app(app)
    return app
```

Inside any view (or template) you can also reach the extension through
`current_app`:

```python
from flask import current_app

current_app.extensions["unirate"].get_rate("USD", "EUR")
```

…or through the convenience helper:

```python
from flask_unirate import get_unirate

get_unirate().get_rate("USD", "EUR")
```

## Jinja filters

Every filter is registered automatically when you call `UniRate(app)` /
`init_app(app)`:

| Filter | Example | Result |
|---|---|---|
| `to_currency(target, base=None)` | `{{ 100|to_currency('EUR') }}` | converts from the configured default base (USD) to EUR |
| `convert_currency(base, target)` | `{{ 100|convert_currency('USD', 'JPY') }}` | explicit base + target |
| `format_money(currency, decimals=2)` | `{{ 1234.5|format_money('USD') }}` | `1,234.50 USD` (BTC/ETH default to higher precision) |
| `to_usd` / `to_eur` / `to_gbp` | `{{ 100|to_eur }}` | shortcut filters bound to specific targets |

Chain them: `{{ amount|to_eur|format_money('EUR') }}`.

## Flask-Caching integration

```python
from flask import Flask
from flask_caching import Cache

from flask_unirate import UniRate

app = Flask(__name__)
app.config.update(
    UNIRATE_API_KEY="...",
    UNIRATE_CACHE_TIMEOUT=300,         # 5 minutes
    CACHE_TYPE="RedisCache",
    CACHE_REDIS_URL="redis://localhost",
)
Cache(app)
UniRate(app)
```

The extension auto-discovers the Cache instance off `app.extensions['cache']`
— so all of Flask-Caching's backends (SimpleCache, RedisCache, MemcachedCache,
FileSystemCache, …) work with no extra wiring. Failures fall through to a
fresh API call rather than raising.

## Errors

Errors come from the underlying `unirate-api` client and propagate
unchanged:

| HTTP | Exception class | Meaning |
|------|------------------|---------|
| 401 | `unirate.exceptions.AuthenticationError` | Missing or invalid API key |
| 404 | `unirate.exceptions.InvalidCurrencyError` | Currency not found |
| 429 | `unirate.exceptions.RateLimitError` | Rate limit exceeded |
| 503 | `unirate.exceptions.APIError` | Service unavailable |
| 403 | (raised as `requests.HTTPError` by `raise_for_status`) | Pro plan required (historical, commodities) |

Wrap the call site in `try / except UnirateError` to catch the whole
family.

## Compatibility

- Python 3.9 – 3.13
- Flask ≥ 2.0
- `unirate-api` ≥ 1.0
- (Optional) `flask-caching` ≥ 2.0

## Related

- [`unirate-api`](https://pypi.org/project/unirate-api/) — base sync
  Python client (this package wraps it).
- [`fastapi-unirate`](https://pypi.org/project/fastapi-unirate/) — async
  sibling for FastAPI.
- [`langchain-unirate`](https://pypi.org/project/langchain-unirate/) —
  LangChain partner package.
- Other UniRate integrations: dbt, Airflow, n8n, Raycast, MCP server.
  Full list at <https://unirateapi.com>.

## License

MIT
