flask-unirate
Flask extension for the UniRate currency-exchange API:
- Drop-in
UniRate(app)extension — follows the standardinit_appfactory pattern, registers itself onapp.extensions["unirate"]. - Jinja filters —
{{ amount|to_currency('EUR') }},{{ amount|convert_currency('USD', 'JPY') }},{{ price|format_money('USD') }}, plusto_usd/to_eur/to_gbpshortcuts. - Optional Flask-Caching integration — set
UNIRATE_CACHE_TIMEOUTand the extension caches latest-rate / supported-currency lookups through whatever Flask-Caching backend you already have. - Wraps the official
unirate-apiPython client; full method surface is reachable throughunirate.clientif 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
pip install flask-unirate
With Flask-Caching support:
pip install "flask-unirate[caching]"
Quick start
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
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:
from flask import current_app
current_app.extensions["unirate"].get_rate("USD", "EUR")
…or through the convenience helper:
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') }}` |
convert_currency(base, target) |
`{{ 100 | convert_currency('USD', 'JPY') }}` |
format_money(currency, decimals=2) |
`{{ 1234.5 | format_money('USD') }}` |
to_usd / to_eur / to_gbp |
`{{ 100 | to_eur }}` |
Chain them: {{ amount|to_eur|format_money('EUR') }}.
Flask-Caching integration
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— base sync Python client (this package wraps it).fastapi-unirate— async sibling for FastAPI.langchain-unirate— LangChain partner package.- Other UniRate integrations: dbt, Airflow, n8n, Raycast, MCP server. Full list at https://unirateapi.com.
License
MIT
Metadata
Release files for flask-unirate 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| flask_unirate-0.1.0.tar.gz | 12.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| flask_unirate-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 21.1 kB
Release files / flask_unirate-0.1.0.tar.gz
| Download URL | flask_unirate-0.1.0.tar.gz |
|---|---|
| Size | 12.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ea085b4dd6e0a2b7b0b1a6891d9d00a82851de032c7b46524e5cb9c985a7eed7
|
|
BLAKE2b-256 checksum How to use checksums |
c6dadaa2fa1a5d793a93ca98841d365427431501f3d4351ed14651a90efdac30
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 9, 2026.
Transparency logRelease files / flask_unirate-0.1.0-py3-none-any.whl
| Download URL | flask_unirate-0.1.0-py3-none-any.whl |
|---|---|
| Size | 9.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9430622b176008595cf422b7bff09adfb04bdba3894d0cc88ec07a7a07acb79d
|
|
BLAKE2b-256 checksum How to use checksums |
076ec46bb8f507f4b2ff4e5df7495334c126fb2349da0e536d29683075273f71
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 9, 2026.
Transparency log