CodeBlanks Logo Icon CodeBlanks

Birleştirme

Tüm katmanları birleştirerek çalışan bir mini framework yazarsın.

Birleştirme: decorator

Son katman, bir decorator ile çağrı günlüğü ekler. log_call, async işlevleri saran bir decorator'dur; functools.wraps ile özgün işlevin kimliğini korur.

import functools

def log_call(func):
    @functools.wraps(func)
    async def wrapper(*args, **kwargs):
        print("çağrıldı: " + func.__name__)
        return await func(*args, **kwargs)
    return wrapper

Bu decorator'ü fetch_tasks'e uygularsın: @log_call. Her çağrıda önce çağrıldı: fetch_tasks yazılır, sonra işlevin sonucu döner. Böylece API çağrıları izlenebilir.

Tüm katmanlar birleşti

Artık tüm katmanlar yerli yerinde:

  1. fetch_tasks: async iskelet + decorator.
  2. fetch_all: asyncio.gather.
  3. SQLAlchemy: Task modeli ve sorgu.
  4. FastAPI: /tasks yolu ve TestClient.
@log_call
async def fetch_tasks():
    return [{...} for t in session.query(Task).all()]

Bu desen, gerçek dünyadaki API'lerin omurgasıdır: async G/Ç, kalıcı veri, HTTP yolu ve gözlemlenebilirlik. Her katman ayrı ayrı test edilebilir; decorator eklendiğinde mevcut davranış bozulmaz.

Uygulama log_call decorator'ünü ekle

Başlangıç kodu, önceki dersin tüm çözümünü içerir (async + SQLAlchemy + FastAPI). Şunları ekle:

  • import functools
  • def log_call(func): decorator tanımla; @functools.wraps(func) ile async def wrapper(*args, **kwargs): gövdesinde önce print("çağrıldı: " + func.__name__) sonra return await func(*args, **kwargs) yapsın; return wrapper
  • @log_call ile async def fetch_tasks(): işlevini yeniden tanımla (DB sorgusu yapan versiyon)

Son satır zaten print(client.get("/tasks").text) olmalı.

Başlangıç kodu

main.py
import asyncio

async def fetch_tasks():
    return [{"id": 1, "title": "Görev 1"}, {"id": 2, "title": "Görev 2"}]

async def fetch_all():
    return await asyncio.gather(fetch_tasks(), fetch_tasks())

from sqlalchemy import create_engine, Column, Integer, String, StaticPool
from sqlalchemy.orm import declarative_base, sessionmaker

Base = declarative_base()

class Task(Base):
    __tablename__ = "tasks"
    id = Column(Integer, primary_key=True)
    title = Column(String)

engine = create_engine(
    "sqlite:///:memory:",
    connect_args={"check_same_thread": False},
    poolclass=StaticPool,
)
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
session.add(Task(id=1, title="Görev 1"))
session.add(Task(id=2, title="Görev 2"))
session.commit()

from fastapi import FastAPI
from fastapi.testclient import TestClient

app = FastAPI()

# functools'ü içe aktar
# log_call adlı bir decorator tanımla (functools.wraps ile saran async wrapper)
# wrapper önce işlev adını 'çağrıldı:' önekiyle yazdırsın, sonra func'ı await'le
# @log_call ile fetch_tasks'i DB sorgusu yapacak şekilde yeniden tanımla
# app.get dekoratörüyle '/tasks' yoluna read_tasks'i bağla, asyncio.run(fetch_tasks()) döndürsün
# TestClient ile '/tasks' yolunu sorgula ve gövdeyi yazdır

Örnek çözüm

main.py
import asyncio

async def fetch_tasks():
    return [{"id": 1, "title": "Görev 1"}, {"id": 2, "title": "Görev 2"}]

async def fetch_all():
    return await asyncio.gather(fetch_tasks(), fetch_tasks())

from sqlalchemy import create_engine, Column, Integer, String, StaticPool
from sqlalchemy.orm import declarative_base, sessionmaker

Base = declarative_base()

class Task(Base):
    __tablename__ = "tasks"
    id = Column(Integer, primary_key=True)
    title = Column(String)

engine = create_engine(
    "sqlite:///:memory:",
    connect_args={"check_same_thread": False},
    poolclass=StaticPool,
)
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
session.add(Task(id=1, title="Görev 1"))
session.add(Task(id=2, title="Görev 2"))
session.commit()

import functools

def log_call(func):
    @functools.wraps(func)
    async def wrapper(*args, **kwargs):
        print("çağrıldı: " + func.__name__)
        return await func(*args, **kwargs)
    return wrapper

@log_call
async def fetch_tasks():
    return [{"id": t.id, "title": t.title} for t in session.query(Task).all()]

from fastapi import FastAPI
from fastapi.testclient import TestClient

app = FastAPI()

@app.get("/tasks")
def read_tasks():
    return asyncio.run(fetch_tasks())

client = TestClient(app)
print(client.get("/tasks").text)

Beklenen çıktı

çağrıldı: fetch_tasks
[{"id":1,"title":"Görev 1"},{"id":2,"title":"Görev 2"}]

Soru ve cevaplar

Dersin sorularını cevaplarıyla birlikte inceleyebilirsin. Kendini denemek istersen dersi interaktif olarak çöz.

1 log_call decorator'ü neden functools.wraps kullanır?

  • Async yapmaya zorlamak için
  • Özgün işlevin kimliğini korumak için Doğru cevap
  • Hızlandırmak için
  • Yorum eklemek için

Açıklama: wraps, __name__ ve __doc__ gibi öznitelikleri kopyalar.

2 wraps çağrısını tamamla

Soru
import functools

def log_call(func):
    @functools.____(func)
    async def wrapper(*args, **kwargs):
        return await func(*args, **kwargs)
    return wrapper
Cevap
import functools

def log_call(func):
    @functools.wraps(func)
    async def wrapper(*args, **kwargs):
        return await func(*args, **kwargs)
    return wrapper

Açıklama: functools.wraps özgün işlevin kimliğini kopyalar.

3 Birleştirme kavramlarını eşleştir

Kavram Anlamı
log_call Çağrı günlüğü yazar
functools.wraps Kimliği korur
@log_call Decorator uygulaması
wrapper Saran işlev

İleri seviye bölümündeki tüm dersler