Blog'a Dön

FastAPI ile Döviz Kuru API'si Nasıl Oluşturulur (asenkron httpx, önbellekleme, Pydantic)

V
Vlado Grigirov
August 11, 2026
FastAPI Python Currency API Exchange Rates Tutorial Fintech

Python'da bir fintech arka ucu, çok para birimli bir ödeme akışı veya dahili bir fiyatlandırma servisi geliştiriyorsanız, er ya da geç kendi döviz kuru uç noktanıza ihtiyaç duyarsınız. FastAPI ile bir döviz kuru API'si oluşturmak, canlı bir kur sağlayıcısını saran, ücretsiz katmanda kalmanız için önbellekleme ekleyen ve diğer servislerinizin çağırabileceği temiz bir /convert uç noktası sunan hızlı, asenkron ve tamamen tip güvenli bir mikroservis verir. FastAPI burada doğal olarak uygundur: gerçek asenkron G/Ç için ASGI üzerine kuruludur, istek ve yanıtları Pydantic ile doğrular ve ücretsiz olarak etkileşimli OpenAPI dokümantasyonu üretir. Bu eğitim tüm yolu kat eder — sağlayıcıya yaptığınız ilk istekten, paylaşılan bir HTTP istemcisi, önbellekli okumalar, Decimal ile para açısından güvenli aritmetik ve tip güvenli hata yönetimi içeren üretime hazır bir dönüştürücüye kadar.

Sonunda, Finexly API dokümantasyonu ve 170'ten fazla para birimi kapsamıyla desteklenen küçük, kendi kendine yeten bir FastAPI servisiniz olacak. Python döviz API eğitimimizi zaten okuduysanız, bu, parçaların gerçek bir ASGI arka ucunda nasıl bir araya geldiğini gösteren servis düzeyindeki tamamlayıcıdır. Django eğitimi ORM ve şablon katmanına dayanırken, FastAPI her şeyi yalın ve async odaklı tutar.

FastAPI neden bir para birimi mikroservisi için harika bir seçim

Para birimi dönüşümü basit görünür — bir tutarı bir kurla çarpmak — ama bunu bir servis olarak iyi yapmak, FastAPI'nin tasarlandığı konulara dokunur:

  • Gerçek asenkron G/Ç. Kur çekmek ağa bağlıdır. async def uç noktaları ve asenkron bir HTTP istemcisiyle tek bir worker, istekler yolda iken çağrı başına bir iş parçacığını bloke etmek yerine birçok eşzamanlı dönüşümü karşılayabilir.
  • Tip güvenli sözleşmeler. Pydantic modelleri gelen sorgu parametrelerini doğrular ve giden JSON'u şekillendirir, böylece bozuk bir amount veya bilinmeyen bir para birimi kodu, mantığınıza ulaşmadan önce net bir 422 ile reddedilir.
  • Ücretsiz etkileşimli dokümantasyon. Yazdığınız her uç nokta /docs adresindeki Swagger UI'da görünür; bu da servisi ekibinizin geri kalanı için kendini belgeleyen hale getirir.
  • Lifespan yönetimi. FastAPI, başlangıçta tek bir HTTP bağlantı havuzu açmak ve kapanışta boşaltmak için temiz bir yer verir — performans için kritik, aşağıda göreceğiniz gibi.

Oluşturduğumuz mimari ince, önbellek bilinçli bir vekildir: servisiniz kur API'sini çağırır, yanıtı kısa bir pencere boyunca önbelleğe alır ve dönüşümleri kendi ön ucunuza veya diğer mikroservislere sunar. Bu desen API anahtarlarınızı sunucu tarafında tutar, sizi sağlayıcının hız sınırlarından yalıtır ve iş kurallarını (yuvarlama, komisyon ekleri, izin listeleri) tek bir yerde eklemenize olanak tanır.

Ön koşullar ve proje kurulumu

Python 3.11 veya daha yenisi gerekir. Bir sanal ortam oluşturun ve bağımlılıkları kurun:

python -m venv .venv
source .venv/bin/activate  # on Windows: .venv\Scripts\activate

pip install "fastapi[standard]" httpx pydantic-settings

fastapi[standard] Uvicorn'u (ASGI sunucusu) ve CLI'yi getirir. httpx asenkron HTTP istemcimizdir ve pydantic-settings yapılandırmayı ortam değişkenlerinden yönetir.

Finexly panelinizden ücretsiz bir API anahtarı alın ve asla sürüm kontrolüne düşmemesi için dışa aktarın:

export FINEXLY_API_KEY="your_api_key_here"

Proje yapısını oluşturun:

fx-service/
├── app/
│   ├── __init__.py
│   ├── config.py
│   ├── client.py
│   ├── models.py
│   └── main.py
└── .env

Adım 1: pydantic-settings ile yapılandırma

Yapılandırmayı tek bir tip güvenli yerde tutun. pydantic-settings ortam değişkenlerini ve bir .env dosyasını okur ve gerekli bir değer eksikse gürültülü biçimde başarısız olur.

# app/config.py
from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_file=".env", env_prefix="FINEXLY_")

    api_key: str
    base_url: str = "https://api.finexly.com/v1"
    cache_ttl_seconds: int = 300
    request_timeout_seconds: float = 10.0


settings = Settings()  # raises at startup if FINEXLY_API_KEY is unset

Yapılandırma nesnesi içe aktarma anında oluşturulduğundan, eksik bir anahtar servisin ilk istekte başarısız olmasını değil, hiç başlamamasını sağlar — bir dağıtım hattında tam da istediğiniz şey.

Adım 2: Lifespan aracılığıyla paylaşılan, asenkron bir HTTP istemcisi

Bu, tüm servisteki en önemli performans kararıdır. İstek başına yeni bir httpx.AsyncClient oluşturmayın. Her istemci bir bağlantı havuzuna sahiptir; çağrı başına bir tane oluşturmak keep-alive bağlantılarını çöpe atar ve her seferinde yeni bir TLS el sıkışmasını zorlar. Bunun yerine, uygulama başlarken bir istemci açın ve onu sürecin tüm ömrü boyunca yeniden kullanın.

Bunu yapmanın modern yolu FastAPI'nin lifespan bağlam yöneticisidir. (Eski @app.on_event("startup") ve @app.on_event("shutdown") dekoratörleri, lifespan lehine FastAPI 0.93+ ile kullanımdan kaldırıldı.) async with kullanmak, başlangıç kesintiye uğrasa bile kapanışta bağlantı havuzunun boşaltılmasını ve soketlerin kapatılmasını garanti eder.

# app/client.py
import httpx
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request

from .config import settings


@asynccontextmanager
async def lifespan(app: FastAPI):
    # Runs once on startup: open a single pooled client
    async with httpx.AsyncClient(
        base_url=settings.base_url,
        headers={"Authorization": f"Bearer {settings.api_key}"},
        timeout=settings.request_timeout_seconds,
    ) as client:
        app.state.http = client
        yield
    # On shutdown, the async-with block closes the pool automatically


def get_client(request: Request) -> httpx.AsyncClient:
    # FastAPI dependency: hand endpoints the shared client
    return request.app.state.http

Artık her uç nokta aynı havuzlanmış istemciyi bir bağımlılık aracılığıyla ödünç alır ve kimsenin bağlantı açmayı veya kapatmayı düşünmesi gerekmez.

Adım 3: Tip güvenli istek ve yanıt modelleri

Pydantic modelleri size doğrulama ve kendini belgeleyen yanıtlar verir. Parasal tutarlar için Decimal kullanımına dikkat edin: ikili kayan noktalar ondalık kesirleri tam olarak temsil edemez ve parada yuvarlama hataları bir merak değil, gerçek bir hatadır.

# app/models.py
from decimal import Decimal
from pydantic import BaseModel, Field


class ConversionResult(BaseModel):
    base: str = Field(..., examples=["USD"])
    quote: str = Field(..., examples=["EUR"])
    amount: Decimal = Field(..., examples=["100.00"])
    rate: Decimal = Field(..., examples=["0.92"])
    converted: Decimal = Field(..., examples=["92.00"])
    as_of: str = Field(..., description="Timestamp of the upstream rate")


class RatesResponse(BaseModel):
    base: str
    rates: dict[str, Decimal]
    as_of: str

FastAPI bu modelleri OpenAPI şemasında işler ve değerleri otomatik olarak dönüştürür/doğrular, böylece anlamsız bir tutar içeren bir istek, işleyiciniz çalışmadan önce reddedilir.

Adım 4: Küçük bir TTL önbelleğiyle kurları çekmek

Kurlar milisaniyeden milisaniyeye değişmez ve sağlayıcı API'sini her istekte dövmek hem yavaştır hem de kotanızı hızla tüketmenin bir yoludur. Yaşam süreli (TTL) küçük bir bellek içi önbellek bunu yumuşatır. Tek süreçli bir servis için sözlük tabanlı bu önbellek yeterlidir; birden çok worker için çağrı noktalarını değiştirmeden sonra onu Redis ile değiştirin.

# app/main.py
import time
from decimal import Decimal, ROUND_HALF_UP

import httpx
from fastapi import Depends, FastAPI, HTTPException, Query

from .client import lifespan, get_client
from .config import settings
from .models import ConversionResult

app = FastAPI(title="FX Service", lifespan=lifespan)

# Simple in-memory cache: {base_currency: (expiry_timestamp, rates_dict)}
_cache: dict[str, tuple[float, dict]] = {}


async def fetch_rates(base: str, client: httpx.AsyncClient) -> dict:
    base = base.upper()
    now = time.monotonic()
    cached = _cache.get(base)
    if cached and cached[0] > now:
        return cached[1]  # cache hit — no network call

    try:
        resp = await client.get("/latest", params={"base": base})
        resp.raise_for_status()
    except httpx.HTTPStatusError as exc:
        code = exc.response.status_code
        if code == 429:
            raise HTTPException(503, "Upstream rate limit reached; try again shortly")
        if code in (401, 403):
            raise HTTPException(502, "Upstream authentication failed")
        raise HTTPException(502, "Upstream rates provider error")
    except httpx.RequestError:
        raise HTTPException(504, "Could not reach rates provider")

    rates = resp.json()["rates"]
    _cache[base] = (now + settings.cache_ttl_seconds, rates)
    return rates

Sağlayıcı hatalarının sizin çağıranlarınız için anlamlı HTTP durum kodlarına nasıl çevrildiğine dikkat edin. Sağlayıcıdan gelen bir 429, yeniden deneme ipucuyla bir 503 olur; bir kimlik doğrulama hatası bir 502 olur, böylece kimlik bilgisi ayrıntılarını asla aşağı akışa sızdırmazsınız. Para birimi API'lerinde önbellekleme ve hata yönetimi üzerine tamamlayıcı kılavuzumuz bu desenleri daha derinlemesine ele alır.

Adım 5: /convert ve /rates uç noktaları

Artık uç noktaların kendisi minicik, çünkü tüm zor iş fetch_rates içinde yaşıyor. Sorgu parametrelerini FastAPI'nin Query'siyle doğrular, ondalık açısından güvenli aritmetik yapar ve tip güvenli modeller döndürürüz.

@app.get("/convert", response_model=ConversionResult)
async def convert(
    base: str = Query(..., min_length=3, max_length=3),
    quote: str = Query(..., min_length=3, max_length=3),
    amount: Decimal = Query(..., gt=0),
    client: httpx.AsyncClient = Depends(get_client),
):
    base, quote = base.upper(), quote.upper()
    rates = await fetch_rates(base, client)

    if quote not in rates:
        raise HTTPException(400, f"Unsupported currency: {quote}")

    rate = Decimal(str(rates[quote]))
    converted = (amount * rate).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)

    return ConversionResult(
        base=base, quote=quote, amount=amount,
        rate=rate, converted=converted, as_of="latest",
    )


@app.get("/health")
async def health():
    return {"status": "ok"}

Yerelde çalıştırın:

fastapi dev app/main.py

Ardından etkileşimli Swagger UI için http://127.0.0.1:8000/docs adresini açın veya uç noktayı doğrudan çağırın:

curl "http://127.0.0.1:8000/convert?base=USD&quote=EUR&amount=100"
{
  "base": "USD",
  "quote": "EUR",
  "amount": "100",
  "rate": "0.92",
  "converted": "92.00",
  "as_of": "latest"
}

Artık çalışan bir para birimi mikroservisiniz var. Kendi dönüşüm aritmetiğinizi çalıştırmamayı tercih ederseniz, Finexly ayrıca barındırılan bir para birimi dönüştürücü ve çağırabileceğiniz doğrudan bir dönüşüm uç noktası da sunar.

Adım 6: Ondalık hassasiyet ve ondalıksız para birimleri hakkında bir not

İki para tuzağı neredeyse her para birimi servisini yakalar:

  1. Tutarlar için asla ikili kayan nokta kullanmayın. Kayan nokta aritmetiğinde 0.1 + 0.2, 0.3 değildir. Kurları ve tutarları Decimal(str(value)) ile ayrıştırın ve son sonucu yukarıdaki gibi kuantalayın.
  2. Her para biriminin iki ondalık basamağı yoktur. ISO 4217 para birimi başına alt birimler tanımlar: JPY ve KRW sıfır ondalık basamağa sahipken, BHD ve KWD üç basamağa sahiptir. .quantize(Decimal("0.01"))'i sabit kodlamak yen ve dinarı sessizce yanlış yuvarlar. Üretim servisi, her para birimi için doğru üssü aramalı ve buna göre kuantalamalıdır.

Bunu doğru yapmak, bir demo ile gerçek işlemlerin önüne koyabileceğiniz bir şey arasındaki farktır.

Adım 7: Servisi test etmek

HTTP istemcisi bir bağımlılık olarak enjekte edildiğinden testler kolaydır — bağımlılığı bir mock ile geçersiz kılın ve asla ağa dokunmayın:

# test_convert.py
from fastapi.testclient import TestClient
from app.main import app, fetch_rates

async def fake_rates(base, client):
    return {"EUR": "0.92", "GBP": "0.79"}

def test_convert(monkeypatch):
    monkeypatch.setattr("app.main.fetch_rates", fake_rates)
    with TestClient(app) as c:
        r = c.get("/convert?base=USD&quote=EUR&amount=100")
        assert r.status_code == 200
        assert r.json()["converted"] == "92.00"

TestClient lifespan olaylarını sizin için çalıştırır, böylece paylaşılan istemci tam olarak üretimdeki gibi kurulur ve sökülür.

Üretime doğru: ölçekleme ve hız sınırları

Yayınlamadan önce planlanacak birkaç şey:

  • Birden çok worker. Birkaç worker'lı Uvicorn/Gunicorn altında bellek içi önbelleğiniz süreç başınadır. Paylaşılan bir önbellek için Redis'e geçin, böylece bir worker tarafından çekilen bir kur hepsine hizmet eder. fetch_rates dikişi bunu tek fonksiyonluk bir değişiklik yapar.
  • Sağlayıcı kotası. Önbellekleme ilk savunma hattınızdır. 5 dakikalık bir TTL ile, kaç dönüşüm sunarsanız sunun, bir taban para birimi saatte en fazla 12 sağlayıcı çağrısına mal olur. Planınızın sınırlarını fiyatlandırma sayfasında inceleyin ve TTL'yi katmanınıza uygun ayarlayın.
  • Geçmiş kurlar. Faturalar, raporlar veya denetim izleri için belirli bir tarihin kurlarını sorgulayan bir /historical uç noktası isteyeceksiniz. Aynı istemci ve önbellek desenleri geçerlidir; yalnızca önbelleği (base, date) ile indeksleyin.
  • Gözlemlenebilirlik. Önbellek isabet/ıska oranlarını ve sağlayıcı gecikmesini kaydedin, böylece TTL'yi gerçek verilerle ayarlayabilirsiniz.

Hâlâ bir sağlayıcı seçiyorsanız, para birimi API'lerini karşılaştırma sayfamız kapsamı, sınırları ve fiyatları yan yana sunar.

Sık Sorulan Sorular

FastAPI'de requests yerine neden httpx kullanmalı?

requests senkrondur ve olay döngüsünü bloke eder, bu da asenkron bir çerçevenin amacını boşa çıkarır. httpx, bağlantı havuzu, zaman aşımları ve yeniden denemeli taşımalar içeren tamamen asenkron bir istemci (httpx.AsyncClient) sunar, böylece diğer istekleri bloke etmeden doğrudan FastAPI'nin async def uç noktalarına takılır.

httpx istemcisini istek başına mı yoksa bir kez mi oluşturmalıyım?

Bir kez. Lifespan bağlam yöneticisinde tek bir httpx.AsyncClient oluşturun ve tüm isteklerde yeniden kullanın. İstek başına bir istemci bağlantı havuzunu çöpe atar ve her çağrıda yeni bir TLS el sıkışmasını zorlar; bu yük altında ölçülebilir biçimde daha yavaştır ve soketleri tüketebilir.

Para birimi API'sinin hız sınırına ulaşmayı nasıl önlerim?

Sağlayıcı yanıtlarını kısa bir TTL ile (örneğin 5 dakika) önbelleğe alın. Kurlar yavaş hareket ettiğinden, taban para birimi başına önbellekleme binlerce kullanıcı dönüşümünü saatte bir avuç sağlayıcı çağrısına indirir. Çok worker'lı dağıtımlar için önbelleğin süreçler arasında paylaşılması için Redis kullanın.

Farklı ondalık basamağa sahip para birimlerini nasıl ele alırım?

Python'un Decimal türünü kullanın, asla float değil, ve sonucu ISO 4217'ye göre her para biriminin doğru alt birim sayısına kuantalayın. JPY ve KRW sıfır ondalık kullanır, çoğu iki kullanır ve BHD/KWD üç kullanır. İki ondalık sabit kodlamak yerine para birimi başına üssü arayın.

Bu deseni Flask veya Django gibi senkron bir çerçeve için kullanabilir miyim?

Önbellekleme ve Decimal desenleri doğrudan aktarılır, ancak paylaşılan istemci ve asenkron uç nokta kısımları FastAPI'ye özgüdür. Django için, çerçevenin önbellek arka ucunu ve ORM'sini kullanan özel Django para birimi API eğitimimize bakın.

Kurmaya başlayın

Artık FastAPI'de tam, asenkron, önbellek bilinçli bir para birimi mikroservisiniz var — Pydantic ile tip güvenli, Decimal ile güvenli ve sağlayıcı hatalarına dayanıklı. Bir oturuşta okunacak kadar küçük ve gerçek bir üretim servisine büyüyecek kadar yapılandırılmış.

Projenize gerçek zamanlı döviz kurlarını entegre etmeye hazır mısınız? Ücretsiz Finexly API anahtarınızı alın — kredi kartı gerekmez. 170'ten fazla para birimini kapsayan cömert bir ücretsiz katmanla başlayın ve trafiğiniz büyüdükçe ölçeklendirin. Önce keşfetmeyi mi tercih edersiniz? Ücretsiz para birimi API'si genel bakışına göz atın veya API dokümantasyonuna dalın.

Vlado Grigirov

Senior Currency Markets Analyst & Financial Strategist

Vlado Grigirov is a senior currency markets analyst and financial strategist with over 14 years of experience in foreign exchange markets, cross-border finance, and currency risk management. He has wo...

View full profile →