Python SQLAlchemy ORM ile Tanışma
Selamlar, bu yazımda Python dünyasının en çok kullanılan ORM kütüphanesi olan SQLAlchemy'ye kısa ama dolu bir giriş yapacağız. Konuyu uzatmadan; ne işe yarıyor, en küçük bir model nasıl tanımlanıyor, session (oturum) nedir, basit CRUD nasıl yazılıyor ve hangi durumda raw SQL'e dönmek daha mantıklı - bunları konuşacağız. Hadi başlayalım.
SQLAlchemy ORM nedir?
ORM yani Object Relational Mapping (nesne-ilişkisel eşleme), veritabanı tablolarını Python sınıflarıyla, satırları da bu sınıfların nesneleriyle eşleyen bir yaklaşım. Bunun bize kazandırdığı şey şu: SELECT, INSERT, UPDATE yazmak yerine session.add(user) deyip işin altından kalkıyoruz.
SQLAlchemy aslında iki katmanlı bir kütüphane. Altta Core var; SQL ifadelerini Python objeleriyle inşa etmeni sağlayan, ORM'siz de kullanabileceğin bir katman. Üstte ORM var; Core'un üzerine kurulmuş, model-nesne eşlemesini yapan kısım. Çoğu uygulama doğrudan ORM ile konuşur, performans kritik bir sorgu olduğunda Core'a iner.
Engine ve session kurulumu
İlk yapılacak iş bir engine oluşturmak. Engine, veritabanına açılan bağlantı havuzunu (connection pool) yöneten obje. Sonra bir sessionmaker ile session fabrikamızı tanımlıyoruz:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base
DATABASE_URL = 'postgresql://user:pass@localhost:5432/myapp'
engine = create_engine(
DATABASE_URL,
pool_size=10,
pool_pre_ping=True,
echo=False,
)
SessionLocal = sessionmaker(bind=engine, autoflush=False)
Base = declarative_base()
pool_pre_ping=True'yu özellikle koymanızı tavsiye ederim; bağlantı kopuklarına karşı sessiz bir sigorta gibi çalışıyor. echo=True yaparsan bütün SQL'i konsola döker, geliştirme sırasında işine yarar ama production'da kapat.
Model tanımlamak
Modeller Base'i miras alan sınıflar. Her sütun bir Column. Hadi minimum bir kullanıcı modeli yazalım:
from sqlalchemy import Column, Integer, String, Boolean, DateTime
from sqlalchemy.sql import func
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
email = Column(String(255), unique=True, nullable=False, index=True)
username = Column(String(100), unique=True, nullable=False)
is_active = Column(Boolean, default=True, nullable=False)
created_at = Column(DateTime(timezone=True), server_default=func.now())
server_default=func.now() önemli bir detay - timestamp'i Python tarafında değil, veritabanı tarafında ürettiriyor. Bu, farklı sunucu saat dilimleri arasında kaymayı engelliyor.
Temel CRUD
Bütün okuma-yazma işleri session üzerinden gidiyor:
db = SessionLocal()
# Create
new_user = User(email='ali@example.com', username='ali')
db.add(new_user)
db.commit()
db.refresh(new_user)
# Read
user = db.query(User).filter(User.email == 'ali@example.com').first()
all_users = db.query(User).filter(User.is_active == True).all()
# Update
user.username = 'ali_new'
db.commit()
# Delete
db.delete(user)
db.commit()
db.close()
commit çağrılana kadar değişiklikler sadece bellekte. refresh ise veritabanının ürettiği değerleri (otomatik id, created_at gibi) nesneye geri yüklüyor.
Bir küçük ilişki örneği
İki modeli birbirine bağlamak için relationship ve ForeignKey'i birlikte kullanıyoruz:
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship
class Post(Base):
__tablename__ = 'posts'
id = Column(Integer, primary_key=True)
title = Column(String(200), nullable=False)
user_id = Column(Integer, ForeignKey('users.id'))
author = relationship('User', back_populates='posts')
User.posts = relationship('Post', back_populates='author')
Artık user.posts dediğinizde o kullanıcının yazılarına, post.author dediğinizde de yazıyı yazana doğrudan erişebiliyorsunuz.
ORM ne zaman doğru tercih, ne zaman değil?
Açıkçası bu konuda biraz fanatizm var iki tarafta da. Benim kanaatim şu:
ORM iyi seçim ise: CRUD ağırlıklı uygulamalar, admin panelleri, kullanıcı yönetimi, küçük-orta API'ler. Tip güvenliği, refactor kolaylığı, model değişimini migration ile takip etme - hepsi ciddi kazanım.
ORM kötü seçim ise: Karmaşık raporlama sorguları, pencere fonksiyonları, CTE zincirleri, ya da on milyonlarca satırı tarayan analitik join'ler. Bunları ORM ile yazmaya çalışmak hem kodun okunaksız olmasına hem de planner'ın saçma planlar üretmesine yol açar. O an SQLAlchemy Core ya da düpedüz raw SQL daha sağlıklı.
Sık karşılaşılan tuzaklar
- N+1 query problemi: Bir liste döndürüp her elemanı için
user.posts'a erişmek N tane ek sorgu üretir.joinedloadveyaselectinloadile baştan eager load edin. - Session'ı uzun tutmak: Web request'lerinde her request başına bir session açıp sonunda kapatın. Uzun yaşayan session'lar bağlantı havuzunu tüketir.
commitileflushkarıştırmak:flushSQL'i gönderir ama transaction açık kalır.commithem flush yapar hem transaction'ı kapatır. Test ortamındaflush+ rollback işine yarayabilir.
Kapanış
Bu yazıda SQLAlchemy ORM'in temel kurulumuna, model tanımına, session ve CRUD'a baktık. Bence küçük bir API yazıyorsanız doğrudan ORM ile başlayın; karmaşık raporlama gerektiğinde Core'a inersiniz. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.
