Not: SQLAlchemy tabanlı SQLModel'ı incelemek isteyebilirsiniz.
Resmi belge için: Peewee Docs
Aşağıdaki kavramları/teknolojileri en azından temel seviyede bildiğinizi varsayıyorum:
- Komut Satırı (basit dosya/dizin işlemleri seviyesinde)
- Python Programlama Dili
- Nesne Tabanlı Programlama (class, instance ve object seviyesinde)
- İlişkisel Veritabanı (anlamanızı kolaylaşırır)
Bu yazıda, dilim döndüğünce, bilgim ölçüsünde Python için kolay ama etkili bir ORM olan Peewee'ye hızlı bir giriş yapıyoruz.
Yazı boyunca, kitaplık veritabanı (Github) oluşturarak Peewee'nin temel özelliklerini bu örnek üzerinden görmüş olacağız, umarım faydalı olur.
İçerik:
- Neden Peewee?
- Senaryo
- Hazırlık
- Kurulum
- Veritabanı Tanımlama
- Tabloları Modelleme
- CRUD İşlemleri
- JOIN İşlemleri
NEDEN PEEWEE?
Kolay; öğrenme eğrisi düşük.
Yeterli konsept; sahip olduğu konseptler, muhtemelen işinizi görecek kadar yeterli.
Başlangıç için ideal; ORM mantığını kavramak ve kısa zaman içerisinde elle tutulur çalışmalar ortaya çıkarmak mümkün. Bu, motivasyonu arttırabilir.
Bilinir; Python camiasında bilinen bir ORM, yani bir şekilde destek bulursunuz. (Fakat popüler değil)
Türkçe kaynak katkısı yapmayı amaçladım.
SENARYO
Modellerimiz (Veritabanı Tablolarımız):
- Kitap
- Okuyucu
- OkunanKitap
- Yazar
- KitapDetay
Ulaşmak istediğimiz bazı veriler:
- Kitap listesi.
- Kitabı okuyanlar kimler?
- Okuyucu listesi ve her birinin kaç kitap okuduğu.
- Okuyucu hangi kitapları okudu?
- Kitap bilgileri.
HAZIRLIK
Hatırlatma!
Windows kullanıcılarıpython3yerine,pykullanmalıdır.
Komut satırını başlatıyoruz...
kitaplik isminde bir dizin/klasör oluşturup, geçiş yapalım:
mkdir kitaplik
cd kitaplik
KURULUM
Python Paket Yöneticisi (PIP) ile kurmak için:
pip3 install peewee
Sanal Ortama Kurulum | Bu adımı geç
Önemli! Bazı GNU/Linux dağıtımlarında
python3-venvtarzı bir paket kurmak gerekebiliyor.
1) Sanal ortamı oluşturalım:
python3 -m venv env
Hatırlatma!
"env" yerine istediğiniz bir ismi de yazabilirsiniz (ör: sanal_peewe.) Bir sonraki adımda env yerine belirlediğiniz ismi yazmanız gerekir.
2) Sanal ortamı aktif hale getirelim:
source env/bin/activate
Hatırlatma! Eğer Windows kullanıyorsanız şu komutu vermelisiniz:
env\Scripts\activate.bat
3) Paket yöneticimizi (PIP) güncelleyelim:
python3 -m pip install --upgrade pip
4) Ve kurulum komutumuzu verelim:
python3 -m pip install peewee
Böylece kitaplik klasörümüze sanal ortamımızı, sanal ortamımıza da Peewee'yi kurmuş olduk.
VERİTABANI TANIMLAMA
Kodlamaya başlıyoruz...
0) VS Code kullanıyorsak editörümüzü, "kitaplik" dizinimiz üzerinde başlatalım:
code .
1) models.py isminde bir dosya oluşturalım ve açalım.
2) peewee modülünü pw olarak kullanmak üzere models.py dosyamıza tanımlayalım/aktaralım:
import peewee as pw
3) Veritabanımızı tanımlayalım:
import peewee as pw
db = pw.SqliteDatabase("kitaplik.db")
Ek bilgi
Veritabanını açmak içindb.connect(), kapatmak içindb.close()kullanabiliriz.
TABLOLARI MODELLEME
Peewee'de ilişkisel veritabanına karşılık gelen kavramlar şu şekildedir:
| Kavram | Karşılığı |
|---|---|
| Model Sınıfı (Model Class) | Veritabanı Tablosu |
| Kolon/Alan Örneği (Field Instance) | Veritabanı Tablo Kolonu |
| Model Örneği (Model Instance) | Veritabanı Tablo Satırı |
Şimdi, yukarıdaki bilgileri senaryomuz üzerinde görelim.
İlişkisel veritabanında "kitap", "okuyucu" ve "okunan_kitap" isimli tablolar oluşturmanın Peewee'deki karşılığı şu şekilde olur:
1) Tablomuzu peewee.Model sınıfından türeterek, sınıf olarak tanımlarız:
class Kitap(pw.Model):
Ek bilgi
Şu güncellemeyi incelemek isteyebilirsiniz: 3.15.4
2) Kolonlarımızı/Alanlarımızı peewee.NameField sınıflarını kullanarak tanımlarız:
class Kitap(pw.Model):
isim = pw.CharField(max_length=250, null=False, unique=True)
Yukarıda
isimkolonu/alanı;
- En fazla 250 karakter olabilir.
- Boş olamaz.
- Tekrar edemez (benzersiz olmalıdır.)
3) Modelimizin hangi veritabanını kullanacağını ve diğer ayarları, alt bir Meta sınıfı yazarak tanımlarız:
class Kitap(pw.Model):
isim = pw.CharField(max_length=250, null=False, unique=True)
Class Meta:
database = db
legacy_table_names = False
Yukarıda
Kitapmodelimiz;
idisminde ve her kayıtta değeri otomatik olarak 1 er artacak bir alan/kolon oluşacak:
id = pw.IntegerField(primary_key=True)
- Veritabanı olarak -daha önce tanımladığımız-
dbdeğişkenini kullanacak.- Veritabanında tablo ismi,
kucuk_isimbiçiminde oluşacak.
4) Şimdi Okuyucu modelimizi tanımlayabiliriz.
Ama öncesinde models.py dosyamızın baş tarafına Modelimiz isminde şöyle bir sınıf tanımlayalım:
import peewee as pw
db = pw.SqlDatabase("kitaplik.db")
class Modelimiz(pw.Model):
class Meta:
database = db
legacy_table_names = False
Kaldığımız yerden devam edelim ve Okuyucu modelimizi, Modelimiz sınıfından türeterek tanımlayalım:
class Okuyucu(Modelimiz):
isim = pw.CharField(max_length=250, null=False, unique=True)
Okuyucu modelimiz ile, Kitap modelimiz arasındaki ilişkiyi de tanımlayalım:
class Okuyucu(Modelimiz):
isim = pw.CharField(max_length=250, null=False, unique=True)
kitaplar = pw.ManyToManyField(Kitap, backref="okuyucular")
OkunanKitap = Okuyucu.kitaplar.get_through_model()
Yukarıda;
Metasınıfı yazmamıza gerek kalmadı. ÇünküModelimizsınıfından türeterek tanımladık.peewee.ManyToManyFieldkullanarakOkuyucuveKitaparasında çoka-çok ilişki tanımladık.backrefparametresini kullanarak; kitap üzerinden okuyuculara ulaşabilmeyi sağlayan bir anahtar ifade tanımlamış olduk.OkunanKitap, iki model arasındaki ilişki için gereklidir.
Çoka-çok İlişki?
Çoka-çok ilişki bize şunu söyler; okuyucu, birden fazla kitabı okumuş olabilir.
Aynı şekilde kitabı, birden fazla okuyucu okumuş olabilir.
Ek bilgi
Eğer elimizde okuyucu varsa, okuduğu kitaplara şu şekilde ulaşırız:okuyucu.kitaplarEğer elimizde kitap varsa, kitabı okuyanlara şu şekilde ulaşırız:
kitap.okuyucular
5) Son olarak modellerimizi veritabanına aktarabilmek için, models.py dosyamıza db.create_tables([]) eklememiz gerekiyor:
db.create_tables([
Kitap,
Okuyucu,
OkunanKitap
])
models.py dosyamızın son hali:
Şimdi komut satırımıza dönelim ve modellerimizi veritabanına aktaralım:
python3 models.py
CRUD İŞLEMLERİ
CRUD?
- Create - Oluştur/Ekle
- Read - Oku/Seç/Listele
- Update - Güncelle
- Delete - Sil
Hazırlık
crud.py isminde yeni bir dosya oluşturalım ve içerisine şu şekilde bir yapı tanımlayalım:
Yukarıda;
Sadece istediğimiz kod bloğunu çalıştırabilmek için, dosyamızı fonksiyon parçalarına böldük.
Kayıt Ekleme (Create)
İlk kısım olan, create() fonksiyonumuzu kodluruyoruz...
1) Kitap ve okuyucu kaydı tanımlayalım:
k1 = Kitap(isim="Martin Eden")
k1.save()
Yukarıda;
Kitapmodelimizden yeni bir kitap örneği oluşturduk.save()metodu ile kaydımızı gerçekleştirdik.
2) Bu sefer create metodu ile okuyucu kaydı tanımlayalım:
o1 = Okuyucu.create(isim="Abdullah")
3) Şimdi de o1(Abdullah) okuyucusuna, k1(Martin Eden) kitabını tanımlayalım:
o1.kitaplar.add(k1)
Hatırlatma!
Okuyucumodelimizekitaplarisminde kolon tanımlamıştık.
4) İsterseniz farklı bir yöntem kullanarak yeni bir kitap kaydı tanımlayalım:
kitap_id = Kitap.insert(isim="Cinayet Alfabesi").execute()
Ek bilgi
execute()metodu, kaydı gerçekleştirir veinttipinde benzersiz bir kayıtiddeğeri döndürür.
5) Şimdi Model.get() kullanarak az önceki kitap kaydımıza ulaşalım.
Ardından o1(Abdullah) okuyucusuna k2(Cinayet Alfabesi) kitabımızı tanımlayalım:
k2 = Kitap.get(Kitap.id == kitap_id)
k2.okuyucular.add(o1)
Hatırlatma!
Okuyucumodelimizde yer alankitaplarkolonunun parametresi,backref="okuyucular"şeklindeydi.
Kayıt Listeleme (Read)
read() fonksiyonumuzu kodluyoruz...
1) Model.select() ve for döngüsü kullanarak kitaplarımızı listeleyebiliriz:
kitaplar = Kitap.select()
for kitap in kitaplar:
print(kitap.id, kitap.isim)
"Kitabı oyuyan her bir kişiyi" listelemek için alt for döngüsü tanımlayalım:
for okuyucu in kitap.okuyucular:
print(okuyucu.isim)
Ek bilgi
Model.select().count()veyalen(Model.select())kullanarak, sorgu sonucunun kaç adet kayıt barındırdığını öğrenebiliriz.Model.select().sql()kullanarak da sorgunun SQL karşılığına ulaşabiliriz.
2) Okuyucuları ve okudukları kitap sayısını listeleyelim:
for okuyucu in Okuyucu.select():
print(okuyucu.isim, okuyucu.kitaplar.count())
Buraya kadar yaptığımız değişiklikleri görmek için;
crud.py dosyamızın en altına, fonksiyon çağrımızı ekleyelim:
create()
read()
Şimdi komut satırımıza dönelim ve sonucu görelim:
python3 crud.py
Kayıt Güncelleme (Update)
update() fonksiyonumuzu kodluyoruz...
1) İlk olarak, kitabımıza ulaşmak için gerekli tanımlamayı yapalım:
martin_eden = Kitap.get(Kitap.isim == "Martin Eden")
2) Değişikliğimizi tanımlayalım:
martin_eden.isim = "Martin Ede"
3) Kaydı gerçekleştirmek ve değişikliği ekrana yazdırabilmek için ekleyelim:
martin_eden.save()
print(martin_eden.isim)
4) Şimdi de farklı bir yöntem kullanarak güncelleme tanımlaması yapalım:
Kitap.update(isim="Martin Eden").where(Kitap.id == martin_ed
den.id).execute()
execute()metodu, işlemi gerçekleştirir ve güncellenen satır sayısınıinttipinde döndürür.
5) Hemen ardından değişikliği görebilmek için ekleyelim:
martin_eden = Kitap.get(Kitap.id == martin_eden.id)
print(martin_eden.isim)
5) Son olarak crud.py dosyamızın en sonuna fonksiyon çağrımızı da ekleyelim:
#create()
#read()
update()
Yukarıda;
create()veread()çağrılarımızı "yorum satırına" döndürdük ki çalışmasınlar.
Şimdi komut satırımıza dönelim ve sonucu görelim:
python3 crud.py
Çoklu Kayıt
multi_create() fonksiyonumuzu kodluyoruz...
1) Öncelikle şu şekilde tuple barındıran bir liste tanımlıyoruz:
veriler = [("Musullu Süleyman",),
("İsimsiz",),
("İnsanları Okumak",),
("Bilim Ne değildir?",)]
Yukarıda;
tuplesatır kolonlarını temsil ediyor, örneğimizde tek kolon olduğu için,("Veri1",)şeklinde yazdık.
2) Kitapları kaydetmek için ekleyelim:
Kitap.insert_many(veriler, fields=[Kitap.isim]).execute()
3) Okuyucuları farklı bir yöntemle kaydetmek için, şu şekilde tanımlama yapalım:
veriler = [{"isim": "İsimsiz"},
{"isim": "Sefa"}]
Okuyucu.insert_many(veriler).execute()
Bu seferki örneğimizde
verilerisimli listemiz,dicttipinde kolonlar barındırıyor. Ayrıcainsert_many()metodu,fields=[]parametresi almıyor.
Kaydettiğimiz kitaplara, kaydettiğimiz okuyucuları tanımla işlemiyle devam ediyoruz...
4) Okuyuculara ulaşalım:
isimsiz = Okuyucu.get(Okuyucu.isim.contains("siz"))
abdullah = Okuyucu.get(Okuyucu.isim.startswith("A"))
sefa = Okuyucu.get(Okuyucu.isim.endswith("fa"))
Yukarıda;
- "siz" ifadesi geçen kayıt getirilecek.
- "A" ile başlayan kayıt getirilecek.
- "fa" ile biten kayıt getirilecek.
Ek bilgi
- Bu metotlar,
where()içeriside de kullanılabilir.- Ek olarak;
between(),in_(),not_in(), ... gibi birçok metot mevcut.
5) Aranacak kitap ve tanımlanacak okuyucu listeleri oluşturalım:
aranacaklar = ["isi", "mus", "oku", "bil"]
okuyucular = [isimsiz, [abdullah, sefa], abdullah, [abdullah, sefa]]
Yukarıda;
2 liste de aynı sayıda eleman içermekte, birden fazla okuyucu alt liste içerisinde tanımlanmakta.
6) for döngüsü yardımıyla tanımlamaları gerçekleştirelim:
index = 0
for aranan in aranacaklar:
kitap = Kitap.get(Kitap.isim.contains(aranan))
okuyucu = okuyucular[index]
if type(okuyucu) == list:
for _okuyucu in okuyucu:
kitap.okuyucular.add(_okuyucu)
else:
kitap.okuyucular.add(okuyucu)
index += 1
Yukarıda;
indexdeğişkeni, listelerin index değerini barındırıyor.- Döngü boyunca kitap aranıyor ve bulunan kitap,
kitapdeğişkenine aktarılıyor. Kitabı okuyan okuyucu ise,okuyucularlistesindenindexyardımıylaokuyucudeğişkenine aktarılıyor.- Eğer kitabı okuyan
okuyucubirden fazla ise (yani liste tipindeyse); altfordöngüsü çalışıyor, her bir okuyucu, kitaba tanımlanıyor.- Eğer kitabı okuyan okuyucu tek ise; kitaba okuyucu tanımlanıyor.
- Döngü boyunca
indexdeğeri 1 arttırılıyor.
Kayıt Silme (Delete)
Delete() fonksiyonumuzu kodluyoruz...
1) Silinecek kayıtarımızı tanımlayalım:
kitap = Kitap.get(Kitap.isim == "İsimsiz")
okuyucu = Okuyucu.get(Okuyucu.isim == "İsimsiz")
2) Okuyucuya tanımlanan kitabı silebilmek için ekleyelim:
okuyucu.kitaplar.remove(kitap)
Ya da tam tersi:
kitap.okuyucular.remove(okuyucu)
Ek bigi
clear()metodu ilişkili bütün kayıtları siler.
3) Şimdi kitabı silebilmek için tanımlalım:
kitap.delete_instance()
4) Son olarak, okuyucuyu farklı şekilde silebilmek için ekleyelim:
Okuyucu.delete().where(Okuyucu.id == okuyucu.id).execute()
execute()metodu, işlemi gerçekleştirir ve silinen satır sayısınıinttipinde döndürür.
JOIN İŞLEMLERİ
JOIN?
Birden fazla modelden tek seferde veri çekilmesi gerektiğinde devreye join işlemi girer. Modellerimize tanımladığımız peewee.ForeignKeyField, peewee.ManyToManyField gibi ilişki kolonları sayesinde modelleri birleştirebiliyoruz.
Hazırlık
1) join.py isminde yeni bir dosya oluşturalım ve içerisine şu şekilde bir yapı tanımlayalım:

Yukarıda;
- Tarih işlemleri için
datetimemodülünü içe aktardık.models.pydosyamızda yer alan ve ihtiyacımız olacak modelleri içe aktardık.- İlk defa
peewee.IntegerField,peewee.DateField,peewee.FloatFieldkullanan modeller tanımladık.peewee.ForeignKeyFieldkullanarak modeller arası bire-bir(Kitap-KitapDetay) ve bire-çok(Yazar-Kitap) ilişkiler tanımladık.
Bire-bir İlişki?
Bire-bir ilişki bize şunu söyler; bir kitabı, bir yazar yazabilir veya bir kitabın, bir tane fiyatı olabilir.
Yani KitapDetay modelinde yer alan kitap kolonu, bir kez kullanılabilir, aynı kitap tekrar edemez.
class KitapDetay(Modelimiz):
kitap = pw.ForeignKeyField(Kitap, backref="kitaplar", primary_key=True)
Yukarıda;
primary_key=Trueparametresi sayesinde; benzersizidkolonu oluşturulmaz yerinekitap_idayarlanır. kitap_id,Kitapmodelininidkolonununa karşılık gelir. (KitapDetay.kitap_id == Kitap.id)
Bire-çok İlişki?
Bire-çok ilişki bize şunu söyler; yazar, birden çok kitap yazmış olabilir.
Yani KitapDetay modelinde yer alan yazar kolonu, birden fazla kez tekrar edebilir.
class KitapDetay(Modelimiz):
kitap = pw.ForeignKeyField(Kitap, backref="kitaplar", primary_key=True)
yazar = pw.ForeignKeyField(Yazar, backref="yazarlar")
Somut bir örnek olarak;
"Martin Eden" kitabı bir kere yer alabilirken, "Jack London" yazarı birden fazla kez yer alabilir. (Jack London'ın başka bir kitabını eklemek isteyebiliriz ;)
2) Daha önceki bilgilerimizi kullanarak create() fonksiyonumuzu kodlayalım:
Yukarıda;
- Yeni yazarlar kaydedilecek.
kitaplarlistesi üzerindefordöngüsü çalışacak. Döngü boyunca; "kitap" ve "okuyucu" bilgileriModel.get()ve -aynı görevi gören-Model.select().where().get()ile alınacak.indexdeğişkeni sayesinde karşılık gelen "tarih" ve "fiyat" bilgileri de alınacak, ardından bütün bu bilgilerdicttipindeverilerlistesine eklenecek.- Son olarak döngü sonucunda oluşan
verilerlistesi,KitapDetaymodelimize kaydedilecek.
Kitap Detaylarını Listeleme
Bunun için KitapDetay, Kitap ve Yazar modellerini birleştirmemiz gerekecek.
1) İlk tanımlamamızı gerçekleştirelim:
kitaplar = (KitapDetay
.select(Kitap.isim,
Yazar.isim.alias("yazar"),
KitapDetay.tarih,
KitapDetay.fiyat))
Yukarıda;
- Sorgumuzu
()içerine alarak, çok satırlı bir şekilde yazım gerçekleştirebildik.alias()metodu, kolonlara arzuladığımız bir isim verebilmeyi sağlıyor. (Örneğimizde hem kitap ismi, hem de yazar ismi aynı anda yer aldığı için, karışmasını engelledik.)
2) join() metodunu kullanarak Kitap modelimizi, switch() ve join() metotlarıyla da Yazar modelimizi birleştirmek için parantezin içerisine ekleyelim:
...
.join(Kitap)
.switch(KitapDetay)
.join(Yazar))
Yukarıda;
switch()metodu sayesindeYazarmodeli,Kitapmodeliyle değilKitapDetaymodeliyle birleşecek. Tersi durumda bu iki tablo arasında bir ilişki olmadığı için hata alacaktık.Ek bilgi
- Yalnızca
where()kullanarak veya şu şekilde zincirleme ifadeler kullanarak filtrelemeler yapmak mümkün:... .join(Yazar) .where(Yazar.isim == "Martin Eden") .where(KitapDetay.fiyat < 60) .orwhere(KitapDetay.tarih.year >= 1995))Hatırlatma; bu ifadelerin
SQLkarşılığını görmek için;kitaplar.sql()kullanabiliriz.
3) Listeyi tarihe göre sıralamak için parantezin içine ekleyelim:
...
.order_by(KitapDetay.tarih.desc()))
Yukarıda;
desc()son tarih başta olsun demek oluyor. Eğer sadece kolon ismi belirtirsek,asc()kullanılmış gibi varsayılır ve bu durumda, ilk tarih başta olsun demiş oluruz.Ek bilgi
Sorgumuzun sonuna;limit(3)(Kaç adet kayıt listeleneceği),distinct()(Yalnızca tekrar etmeyen kayıtların listeleneceği) gibi metotlar ekleyebiliriz.
4) Son olarak listeleme işlemini gerçekleştirmek için tanımlayalım:
for kitap in kitaplar.dicts():
print(kitap)
Yukarıda;
dicts(), satırları "sözlük veri tipinde(dict)" almamızı sağlar.Ek bilgi
tuples()veyanamedtuples()kullanmak da mümkün.
Okuyucuları Listeleme
read() fonksiyonumuzu kaldığımız yerden kodlamaya devam ediyoruz...
1) Öncelikle bu kez select()'de kullanacağımız kolonları ayrı tanımlayalım:
miktar = pw.fn.COUNT(OkunanKitap.kitap_id).alias("okunan_miktar")
birlesik = Okuyucu.isim.concat("+").concat(Kitap.isim).alias("birlesik")
Yukarıda;
miktardeğişkenine atadığımız kolon;peewee.fn.COUNT()'u kullanarakOkunanKitap.kitap_id'nin kaç adet olduğunu öğrenmemizi sağlayacak.birlesikdeğişkenine atadığımız kolon;concat()metodunu kullanarakOkuyucu.isimveKitap.isimkolonlarını aralarına "+" işareti koyarak birleştirmemizi (tek kolon haline getirmemizi) sağlayacak.Ek bilgi
alias()metodunu modeller için de kullanabiliriz:
model = Model.alias("model_name")
2) Bu adımda okuyucular değişkenimizin sorgusunu tanımlamaya başlayalım:
okuyucular = (Okuyucu
.select(Okuyucu.isim, miktar, birlesik)
3) OkunanKitap ve Kitap modellerimizi birleştirelim:
...
.join(OkunanKitap, pw.JOIN.LEFT_OUTER,
on=(Okuyucu.id == OkunanKitap.okuyucu_id))
.join(Kitap, pw.JOIN.LEFT_OUTER,
on=(Kitap.id == OkunanKitap.kitap_id))
Yukarıda;
join()metodunu, birleşim türü (LELFT_OUTER, RIGHT_OUTER, vb.) ve birleşim tercihi (on=()) ile beraber kullandık.
4) Her bir okuyucunun okuduğu kitap sayısını listelettireceğimiz için, okuyucu grubu tanımlayalım:
...
.group_by(Okuyucu.id)
.order_by(miktar, Okuyucu.isim))
Yukarıda;
order_by()metoduna, birden fazla sıralanacak kolon tanımlanıyor.
5) Son olarak okuyucular listemizi görüntüleyelim:
for okuyucu in okuyucular.objects():
print(okuyucu.isim,
okuyucu.okunan_miktar,
okuyucu.birlesik)
Yukarıda;
objects()her bir satırı nesne olarak kullanmamızı sağlıyor.
6) Şimdi komut satırına dönelim ve sonucu görelim:
python join.py
Böylece bu yazının sonuna geldik. Bir başka yazıda görüşmek üzere...














Oldest comments (0)