"""A local web form for dated reference-currency conversions.""" from datetime import date, datetime, timezone from decimal import Decimal, InvalidOperation, ROUND_HALF_EVEN, localcontext import hmac import math import os import re import secrets import httpx from flask import Flask, render_template_string, request, session from parseapi import ParseAPI, ParseAPIError app = Flask(__name__) app.secret_key = os.environ.get("APP_SECRET") or secrets.token_hex(32) app.config.update(MAX_CONTENT_LENGTH=8192, SESSION_COOKIE_HTTPONLY=True, SESSION_COOKIE_SAMESITE="Lax", TRUSTED_HOSTS=["127.0.0.1", "localhost"]) CURRENCIES = ("USD", "EUR", "GBP", "JPY", "CAD", "AUD", "CHF", "INR") PAGE = """ Currency converter

Currency converter

Convert an amount using the reference rate for your chosen date.

{% if error %}{% endif %}

{{ errors.get('amount', '') }}

{% for field,label in [('base','From'),('quote','To')] %}
{% if errors[field] %}

{{ errors[field] }}

{% endif %}
{% endfor %}
{% if errors.day %}

{{ errors.day }}

{% endif %}
{% if result %}

{{ result.amount }} {{ result.base }} equals

{{ result.converted }} {{ result.quote }}

Rate: {{ result.rate }} ยท Published {{ result.rate_date }}

This is a reference estimate. Card charges, fees and settlement rates can differ.

{% endif %}

Weekends and holidays can use the last published rate on or before your chosen date.

""" @app.after_request def private_response(response): response.headers["Cache-Control"] = "no-store" response.headers["X-Content-Type-Options"] = "nosniff" return response @app.errorhandler(413) def too_large(_error): return "

Request too large

Go back and enter a shorter amount.

", 413 @app.route("/", methods=["GET", "POST"]) def index(): today = datetime.now(timezone.utc).date().isoformat() values = {"amount": "12.50", "base": "EUR", "quote": "USD", "day": today} errors, error, result, status = {}, None, None, 200 token = session.setdefault("csrf", secrets.token_urlsafe(32)) if request.method == "POST": # example:inputs values = { "amount": request.form.get("amount", ""), "base": request.form.get("base", ""), "quote": request.form.get("quote", ""), "day": request.form.get("day", ""), } # /example:inputs submitted = request.form.get("csrf", "") if not submitted.isascii() or not hmac.compare_digest(submitted, token): error, status = "This form expired. Refresh the page and try again.", 403 else: amount_text = values["amount"].strip() if not re.fullmatch(r"-?(?:0|[1-9][0-9]{0,8})(?:\.[0-9]{1,4})?", amount_text): errors["amount"] = "Enter an amount with up to nine whole digits and four decimal places." for field in ("base", "quote"): if values[field] not in CURRENCIES: errors[field] = "Choose one of the listed currencies." try: day = date.fromisoformat(values["day"]) if day.isoformat() != values["day"] or values["day"] > today: raise ValueError() except ValueError: errors["day"] = "Choose a real date on or before today." if errors: status = 422 else: key = os.environ.get("PARSEAPI_KEY", "").strip() if not key or key.startswith(("parse_public_", "parse_app_")): error, status = "The converter is unavailable. Please try again later.", 503 else: base, quote = values["base"], values["quote"] try: # One interactive attempt, with one reused official SDK client. with ParseAPI(api_key=key, timeout=10.0, retries=0) as parse: # example:request exchange = parse.currency.rate(base, quote, date=day.isoformat()) # /example:request if (not isinstance(exchange, dict) or exchange.get("base") != base or exchange.get("quote") != quote or type(exchange.get("rate")) not in (int, float) or not math.isfinite(exchange["rate"]) or exchange["rate"] <= 0): raise ValueError("Unexpected rate response.") applied = date.fromisoformat(exchange.get("date", "")) if applied.isoformat() != exchange["date"] or applied > day: raise ValueError("Unexpected rate date.") # example:precision currency = parse.currency(quote) # /example:precision if (not isinstance(currency, dict) or currency.get("currency") != quote or type(currency.get("digits")) is not int or not 0 <= currency["digits"] <= 6): raise ValueError("Currency precision is unavailable.") # example:fields rate = Decimal(str(exchange["rate"])) rate_date = exchange["date"] digits = currency["digits"] # /example:fields amount = Decimal(amount_text) with localcontext() as context: context.prec = 50 # example:convert converted = (amount * rate).quantize( Decimal(1).scaleb(-digits), rounding=ROUND_HALF_EVEN ) # /example:convert if converted == 0: converted = abs(converted) result = {"amount": format(amount, "f"), "base": base, "quote": quote, "converted": format(converted, "f"), "rate": format(rate, "f"), "rate_date": rate_date} except ParseAPIError as failure: if failure.status == 404: error, status = "No reference rate is available for this pair and date. Try another date.", 422 else: error, status = "The rate could not be checked. Please try again later.", 503 except httpx.RequestError: error, status = "The rate could not be checked. Please try again later.", 503 except (ValueError, InvalidOperation, OverflowError, TypeError): error, status = "The rate response could not be used. Please try again later.", 503 return render_template_string(PAGE, values=values, errors=errors, error=error, result=result, currencies=CURRENCIES, today=today, csrf=token), status if __name__ == "__main__": app.run(host="127.0.0.1", port=int(os.environ.get("PORT", "8000")), debug=False, use_reloader=False)