19. ORM'in kullanımı SQLALCHEMY
Bir önceki bölümde, bazı durumlarda aşağıdaki mimariyle kullanılan SGBD'ten bağımsız kod yazılabileceğinin gösterilmişti:

Bu bölümde, kullanılan SGBD ne olursa olsun SGBD'lere tutarlı bir şekilde erişmek için ORM (Nesne-İlişkisel Eşleştirici) [sqlalchemy]'i kullanacağız. Bir ORM iki şeye olanak tanır:
- bir komut dosyasının SQL komutları vermeden SGBD ile iletişim kurmasını sağlar;
- komut dosyasına her bir SGBD'in kendine özgü özelliklerini gizler;
Mimari şu şekilde olur:
Komut dosyası artık ORM aracılığıyla konektörlerden ayrılmıştır. Sınıflar ve yöntemler aracılığıyla ORM ile iletişim kurar. SQL kodunu çalıştırmaz. Bunu, bağlı olduğu konektörler aracılığıyla ORM gerçekleştirir. Bu konektörlerin özelliklerini komut dosyasından gizler. Dolayısıyla komut dosyası kodu, konektör değişikliğinden (yani SGBD'ten) etkilenmez;
İncelenen komut dosyalarının hiyerarşisi şu şekilde olacaktır:

19.1. ORM ve [sqlalchemy]’in kurulumu
ORM [sqlalchemy], bir Python terminaline kurulması gereken bir Python paketi biçimindedir:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\databases\sqlalchemy>pip install sqlalchemy
Collecting sqlalchemy
Downloading SQLAlchemy-1.3.18-cp38-cp38-win_amd64.whl (1.2 MB)
|| 1.2 MB 3.3 MB/s
Installing collected packages: sqlalchemy
Successfully installed sqlalchemy-1.3.18
19.2. Komut Dosyaları 01: Temel Bilgiler

- [1] içinde, incelenecek komut dosyaları bulunmaktadır. Bu komut dosyaları, [2]'teki şu sınıfları kullanacaktır: BaseEntity, MyException, Personne, Utils;
19.2.1. Yapılandırma
[config] dosyası, uygulamayı şu şekilde yapılandırır:
def configure():
# root_dir
# yapılandırmadaki göreceli yolların mutlak yolu
root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"
# bağımlılıkların mutlak yolları
absolute_dependencies = [
# BaseEntity, MyException, Kişi, Yardımcı Programlar
f"{root_dir}/classes/02/entities",
]
# syspath ayarlanıyor
from myutils import set_syspath
set_syspath(absolute_dependencies)
# sınıfların yapılandırılması
from Personne import Personne
Personne.excluded_keys = ['_sa_instance_state']
# yapılandırma uygulanıyor
return {}
Açıklamalar
-
- satır: [BaseEntity, MyException, Personne, Utils] sınıflarını içeren klasör Python Path'e eklenir;
- 12-13. satırlar: Uygulamanın Python Yolu belirlenir;
- 16-17. satırlar: |BaseEntity| sınıfının [excluded_keys] adlı bir sınıf özniteliği olduğunu hatırlayabilirsiniz. Bu öznitelik, sınıfın sözlüğünde (asdict işlevi) görünmesini istemediğimiz sınıf özelliklerinin eklendiği bir listedir. Burada, [Personne] sınıfının durumundan [_sa_instance_state] özelliğini hariç tutuyoruz. Bunun nedenini birazdan göreceğiz;
19.2.2. [démo] Komut Dosyası
[démo] komut dosyası, ORM ve [sqlalchemy]'in ilk kullanımını göstermektedir:
# uygulamanın yapılandırmasını alıyoruz
import config
config = config.configure()
# ithalatlar
from sqlalchemy import Table, Column, Integer, String, MetaData, UniqueConstraint
from sqlalchemy.orm import mapper
from Personne import Personne
# meta veriler
metadata = MetaData()
# tablo
personnes_table = Table("personnes", metadata,
Column('id', Integer, primary_key=True),
Column('prenom', String(30), nullable=False),
Column("nom", String(30), nullable=False),
Column("age", Integer, nullable=False),
UniqueConstraint('nom', 'prenom', name='uix_1')
)
# eşleme öncesinde Personne sınıfı
personne1 = Personne().fromdict({"id": 67, "prénom": "x", "nom": "y", "âge": 10})
print(f"personne1={personne1.__dict__}")
# eşleme
mapper(Personne, personnes_table, properties={
'id': personnes_table.c.id,
''ad': personnes_table.c.prenom,
'soyadı: personnes_table.c.nom,
'yaş: personnes_table.c.age
})
# kişi1 değiştirilmedi
print(f"personne1={personne1.__dict__}")
# Personne sınıfı değiştirildi – "zenginleştirildi"
personne2 = Personne().fromdict({"id": 68, "prénom": "x1", "nom": "y1", "âge": 11})
print(f"personne2={personne2.__dict__}")
Açıklamalar
- 1-4. satırlar: uygulama yapılandırılır;
- 6-10. satırlar: komut dosyası için gerekli modüller içe aktarılır;
-
- satır: [MetaData], [sqlalchemy]'in bir sınıfıdır;
- 15-22. satırlar: [Table], [sqlalchemy] sınıfının bir alt sınıfıdır. Bu sınıf, bir veritabanındaki tabloyu tanımlamaya yarar. Burada, |MySQL| bölümünde incelenen MySQL [dbpersonnes] veritabanındaki [personnes] tablosunu tanımlayacağız;
-
- satır: İlk parametre [personnes], tanımlanan tablonun adıdır;
-
- satır: İkinci parametre [metadata], 13. satırda oluşturulan [MetaData] örneğidir;
- satır 17-22: Aşağıdaki parametrelerin her biri, [sqlalchemy]'e özgü ancak SQL sözdizimine yakın bir sözdizimi ile tablonun bir sütununu tanımlar;
- her sütun, [sqlalchemy] sınıfının [Column] örneği ile tanımlanır;
-
- parametre sütunun adıdır;
- ikinci parametre, sütunun türüdür;
- sonraki parametreler adlandırılmış parametrelerdir:
-
- satır: [primary_key=True], [id] sütununun [personnes] tablosunun birincil anahtarı olduğunu belirtir;
-
- satır: [nullable=False], tabloya bir satır eklendiğinde bu sütunun mutlaka bir değere sahip olması gerektiğini belirtir;
-
-
- satır 21: Son olarak, [UniqueConstraint] sınıfı bir tekillik kısıtlamasını tanımlamaya olanak tanır. Burada, (soyadı, adı) sütunlarının tabloda benzersiz olması gerektiği belirtilir. [name] adlı özellik, bu kısıtlamaya bir ad verilmesini sağlar. Burada iki durumu birbirinden ayırmak gerekir:
- mevcut bir tablo tanımlanıyorsa, kısıtlamanın adını tablonun özellikleri arasında aramak gerekir (phpMyAdmin veya pgAdmin);
- oluşturulacak bir tablo tanımlanıyor. Bu durumda, istenen adı girin;
-
- 23-25. satırlar: [personne1] adlı bir kişi oluşturulur ve [__dict__] adlı sözlüğü görüntülenir. Burada şunu göreceğiz:
personne1={'_BaseEntity__id': 67, '_Personne__prénom': 'x', '_Personne__nom': 'y', '_Personne__âge': 10}
- 27-33. satırlar: Bir eşleme yapıyoruz, yani [Personne] sınıfı ile [personnes] tablosu arasında bir ilişki kuruyoruz. Bu, esasen bir [propriétés de la classe colonnes de la table] eşlemesidir. [mapper] işlevi burada üç parametre kabul eder:
-
- satır: 1. parametre, eşleme işleminin yapılacağı sınıfın adıdır;
-
- satır: İkinci parametre, eşleştirileceği tablodur. Bu, 16. satırda oluşturulan [Table] nesnesidir;
-
- satır: Üçüncü parametre, burada [properties] adlı bir parametredir. Bu, anahtarları eşlenen sınıfın özellikleri, değerleri ise eşlenen tablonun sütunları olan bir sözlüktür. [personnes_table] tablosunun X sütununu belirtmek için [personnes_table.c.X] yazılır;
-
- 35-36. satırlar: eşleme tamamlandıktan sonra [personne1] kişisini yeniden görüntülüyoruz. Bu kişinin değişmediğini görüyoruz:
personne1={'_BaseEntity__id': 67, '_Personne__prénom': 'x', '_Personne__nom': 'y', '_Personne__âge': 10}
- 37-39. satırlar: Yeni bir kişi olan [personne2] oluşturulur ve görüntülenir. Bunun üzerine şu görüntü elde edilir:
personne2={'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x00000259A6747FA0>, 'id': 68, 'prénom': 'x1', 'nom': 'y1', 'âge': 11}
[__dict__] sözlüğünün büyük ölçüde değiştirildiğini görüyoruz:
- (devam)
- yeni bir [_sa_instance_state] özelliği ortaya çıkıyor. Bunun ORM ve [sqlalchemy] nesnelerinden biri olduğu görülüyor;
- diğer özelliklerin, hangi sınıfa ait olduklarını belirten önekleri kaldırılmıştır;
Dolayısıyla, 27-33. satırlardaki eşleme işleminin [Personne] sınıfını değiştirdiği sonucuna varılabilir.
[Personne] nesnesinin durumunu görüntülemek istediğimizde, genellikle [_sa_instance_state] özelliğine ihtiyacımız olmaz. Bu özellik aslında sadece [sqlalchemy]’in iç işleyişi için vardır ve genellikle bizi ilgilendirmez. Bu nedenle [config] komut dosyasına şunu yazdık:
# sınıfların yapılandırılması
from Personne import Personne
Personne.excluded_keys = ['_sa_instance_state']
19.2.3. [main] komut dosyası
[main] komut dosyası, [sqlalchemy] ile arayüz oluşturarak MySQL ve [dbpersonnes] veritabanlarındaki [personnes] tablosunu işleyecektir. Devamını anlamak için burada kullanılan mimariyi hatırlamak gerekir:

[Database1], [dbpersonnes] veritabanı ise, komut dosyası ile bu veritabanı arasındaki bağlantının iki varlık üzerinden gerçekleştiği görülür:
- SGBD MySQL'teki Python bağlayıcısı;
- SGBD ile MySQL;
[main] komut dosyası, ORM ile iletişim kuracak ve bu da daha sonra Python konektörüyle iletişim kuracaktır. ORM, özellikle SQL komutlarını göndererek, |MySQL| ve |PostgreSQL| paragraflarında açıklanan araçlarla bu konektörle iletişim kurar. [main] komut dosyası, SQL komutlarını kullanmayacaktır. Skript, sınıflar ve arayüzlerden oluşan ORM'in API (Uygulama Programlama Arayüzü) öğesini kullanacaktır.
[main] komut dosyası şu şekildedir:
# uygulama yapılandırılıyor
import config
config = config.configure()
# ithalatlar
from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData, UniqueConstraint
from sqlalchemy.exc import IntegrityError, InterfaceError
from sqlalchemy.orm import mapper, sessionmaker
from Personne import Personne
# veritabanı bağlantı dizesi MySQL
engine = create_engine("mysql+mysqlconnector://admpersonnes:nobody@localhost/dbpersonnes")
# meta veriler
metadata = MetaData()
# tablo
personnes_table = Table("personnes", metadata,
Column('id', Integer, primary_key=True),
Column('prenom', String(30), nullable=False),
Column("nom", String(30), nullable=False),
Column("age", Integer, nullable=False),
UniqueConstraint('nom', 'prenom', name='uix_1')
)
# eşleme
mapper(Personne, personnes_table, properties={
'id': personnes_table.c.id,
''ad': personnes_table.c.prenom,
'soyadı: personnes_table.c.nom,
'yaş: personnes_table.c.age
})
# oturum fabrikası
Session = sessionmaker()
Session.configure(bind=engine)
session = None
try:
# bir oturum
session = Session()
# [personnes] tablosunun silinmesi
session.execute("drop table if exists personnes")
# eşlemeden tablo yeniden oluşturma
metadata.create_all(engine)
# bir ekleme
session.add(Personne().fromdict({"id": 67, "prénom": "x", "nom": "y", "âge": 10}))
# session.commit()
# bir sorgu
personnes = session.query(Personne).all()
# görüntüleme
print("Liste des personnes ---------")
for personne in personnes:
print(personne)
# iki ekleme daha; bunlardan ikincisi (ad, soyad) benzersizliği nedeniyle başarısız oluyor
session.add(Personne().fromdict({"id": 68, "prénom": "x1", "nom": "y1", "âge": 10}))
session.add(Personne().fromdict({"id": 69, "prénom": "x1", "nom": "y1", "âge": 10}))
# bir sorgu
personnes = session.query(Personne).all()
# görüntüleme
print("Liste des personnes ---------")
for personne in personnes:
print(personne)
# oturum doğrulama
session.commit()
except (InterfaceError, IntegrityError) as erreur:
# görüntüleme
print(f"L'erreur suivante s'est produite : {erreur}")
# son oturumun iptali
if session:
print("rollback...")
session.rollback()
finally:
# oturum kaynakları serbest bırakılıyor
if session:
session.close()
Açıklamalar
- 1-4. satırlar: uygulama yapılandırılır;
- 7-9. satırlar: [sqlalchemy] kütüphanesinden bir dizi sınıf ve arayüz içe aktarılır;
-
- satır: [Personne] sınıfı içe aktarılır;
-
- satır: Veritabanına bağlantı dizesi. Şu bilgileri içerir:
- kullanılan SGBD (mysql);
- kullanılan Python bağlayıcısını (nokta olmadan mysql.connector);
- bağlanan kullanıcı (admpersonnes);
- kullanıcının şifresi (nobody);
- SGBD'in bulunduğu makine (localhost = çalıştırılan komut dosyasının bulunduğu makine);
- veritabanının adı (dbpersonnes);
Bu bilgilerle [sqlalchemy], veritabanına bağlanabilir. Kullanılan Python bağlayıcısının önceden kurulmuş olması gerektiği unutulmamalıdır. [sqlalchemy] bunu yapmaz.
- 19-26. satırlar: [personnes] tablosunun açıklaması;
- 28-34. satırlar: [Personne] sınıfı ile [personnes] tablosu arasındaki eşleme;
- satır 36-38: [sqlalchemy] işlemlerinin çoğu bir oturum içinde gerçekleştirilir. [sqlalchemy] oturum kavramı, SQL işlem kavramına yakındır. Oturumlar, 37. satırdaki [sessionmaker] işlevi tarafından döndürülen [Session] sınıfından oluşturulur;
-
- satır: [Session] sınıfı, 14. satırdaki bağlantı dizesi aracılığıyla [dbpersonnes] veritabanıyla ilişkilendirilir;
-
- satır: Bir oturum oluşturulur. Daha önce de belirtildiği gibi, bir oturum bir işlemle ilişkilendirilebilir;
- 45-46. satırlar: [Session.execute] yöntemi, SQL emrinin yürütülmesini sağlar. Bu çok yaygın bir durum değildir, zira daha önce ORM'in SQL dilini kullanmaya gerek kalmadan işlem yapmayı sağladığı belirtilmişti;
- 48-49. satırlar: [metadata.create_all] yöntemi, 17. satırdaki [MetaData] örneğini kullanan tüm tabloların oluşturulmasını sağlar. Elimizde sadece bir tane var: 20-26. satırlarda tanımlanan [personnes] tablosu. [sqlalchemy], bu satırlardaki bilgileri kullanarak tabloyu oluşturacaktır. İşte burada ORM’in ilk avantajı ortaya çıkıyor: SGBD'in özelliklerini gizler. Nitekim, SQL ve [create] komutları, sütunlara atanan türler nedeniyle bir SGBD'ten diğerine çok farklı olabilir. Veri türleri arasında bir standartlaştırma yapılmamıştır. Dolayısıyla [create] sırası, bir SGBD'ten diğerine değişir. Burada, [sqlalchemy] sayesinde:
- istediğimiz tabloyu benzersiz bir şekilde tanımlıyoruz;
- [sqlalchemy], karşısındaki SGBD için uygun olan [create]'i oluşturmayı başarır;
-
- satır: Oturuma bir [Personne] nesnesi eklenir. Bu, onu otomatik olarak veritabanına eklemez. Aslında, bir ORM, veritabanıyla senkronize olmak için kendi kurallarını izler. Her zaman yaptığı sorgu sayısını optimize etmeye çalışır. Bir örnek verelim. Komut dosyası, oturuma iki kişi (kişi1, kişi2) ekler (add) ve ardından bir sorgu yapar: tabloda bulunan tüm kişileri görmek ister. [sqlalchemy] şu şekilde hareket edebilir:
- [personne1]'in eklenmesi bellek içinde gerçekleştirilebilir. Şu an için bunu veritabanına kaydetmeye gerek yoktur;
- aynı durum [personne2] için de geçerlidir;
- ardından [select] türünde bir sorgu gelir. Bu durumda [personnes] tablosundaki tüm satırları almak gerekir. [sqlalchemy], [personne1, personne2]'i veritabanına ekleyip ardından sorguyu gerçekleştirecektir;
Böylece [sqlalchemy], geliştirici için şeffaf bir şekilde optimizasyonlar gerçekleştirecektir.
-
- satır: [select] türünde bir sorgu yapmak için (şunu görmek istiyorum…), [Session.query] yöntemi kullanılır. [query] yönteminin parametresi, sorgulanan tablo ile eşleştirilmiş sınıftır. Bu yöntem, [Query] türünde bir değer döndürür. [Query.all] yöntemi, oturumdaki tüm [Personne] nesnelerini ister. Bu yönteme, [personnes] tablosundaki tüm satırlar, her biri bir [Personne] nesnesi biçiminde döndürülür. Bunu yapmak için [sqlalchemy], [Personne] sınıfı ile [personnes] tablosu arasında yapılan eşlemeyi kullanır. 56. satırın sonucu, [Personne] nesnelerinden oluşan bir listedir;
- 58-61. satırlar: [personnes] listesinin öğeleri görüntülenir. [Personne] sınıfı, [BaseEntity] sınıfından türediği için, burada 61. satırda örtük olarak kullanılan [Personne.__str__] yöntemi, aslında çağıran nesnenin jSON dizesini döndüren [BaseEntity.__str__] yöntemidir. Bu dize, [Personne.asdict] sözlüğündeki jSON dizesidir (bkz. |BaseEntity|). Eşlemeden sonra her bir [Personne] nesnesinde [_sa_instance_state] özelliğini bulacağımızı söylemiştik. Ancak bu özelliğin değeri [BaseEntity] türü değildir. Bu nedenle, [Personne] sınıfının sözlüğünden bu özelliği hariç tutmak gerekir; aksi takdirde görüntüleme “çökecektir”. [config] komut dosyasında da bu şekilde yapılmıştır;
- 63-65. satırlar: Aynı adı ve soyadına sahip iki kişi daha ekleniyor. Oysa bu iki sütunun birleşiminde tekillik kısıtlaması var. Dolayısıyla bir hata oluşması gerekir. İşte bunu görmeye çalışıyoruz;
- 67-68. satırlar: Veritabanındaki tüm kişilerin listesi yeniden isteniyor;
- 70-73. satırlar: ve bunları ekrana yazdırıyoruz;
- 75-76. satırlar: oturum “commit” ile onaylanır. Adından da anlaşılacağı gibi, altta yatan işlem onaylanacaktır;
- çalıştırma sırasında, 65. satırda ortaya çıkan istisna nedeniyle 67-76. satırların yürütülmeyeceğini göreceğiz. Ardından, istisnayı yönetmek için 78-84. satırlara geçeceğiz;
-
- satır: [sqlalchemy], [dbpersonnes] veritabanına bağlanamazsa [InterfaceError] istisnası ortaya çıkar. 65. satırda [IntegrityError] istisnası oluşur;
-
- satır: hata görüntülenir;
- 82-84. satırlar: oturum varsa, iptal edilir. Bu, altta yatan işlemi iptal etmekle eşdeğerdir;
- 85-88. satırlar: hata olsun ya da olmasın, kaynakları serbest bırakmak için oturum her durumda kapatılır;
Çalıştırma sonuçları şöyledir:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/databases/sqlalchemy/01/main.py
Liste des personnes ---------
{"nom": "y", "prénom": "x", "id": 67, "âge": 10}
L'erreur suivante s'est produite : (raised as a result of Query-invoked autoflush; consider using a session.no_autoflush block if this flush is occurring prematurely)
(mysql.connector.errors.IntegrityError) 1062 (23000): Duplicate entry 'y1-x1' for key 'uix_1'
[SQL: INSERT INTO personnes (id, prenom, nom, age) VALUES (%(id)s, %(prenom)s, %(nom)s, %(age)s)]
[parameters: ({'id': 68, 'prenom': 'x1', 'nom': 'y1', 'age': 10}, {'id': 69, 'prenom': 'x1', 'nom': 'y1', 'age': 10})]
(Background on this error at: http://sqlalche.me/e/13/gkpj)
rollback...
Process finished with exit code 0
- 2-3. satırlar: ilk eklemeden sonraki kişi listesi;
- satır 5: aynı adı ve soyadına sahip iki kişi eklendiğinde ortaya çıkan [IntegrityError] istisnası;
- 6-7. satırlar: başarısız olan SQL komutu dikkat çekmektedir. Bu, yapılandırılmış bir INSERT emridir: [sqlalchemy], iki kişiyi tek bir INSERT ile eklemiştir. Burada, gönderilen SQL siparişlerini optimize etmeye çalıştığı görülüyor;
Şimdi phpMyAdmin ile [personnes] tablosunun içeriğine bakalım:

[6]'te tablonun boş olduğu görülüyor. Komut dosyasının oturuma eklediği ilk kişi bile yok. Bunun nedeni, oturumun bir işlem içinde gerçekleşmiş olması ve bu işlemin [main] komut dosyasının [except] cümlesinde geri alınmış olmasıdır.
Şimdi [main]'te şu değişikliği yapalım:
# ekleme
session.add(Personne().fromdict({"id": 67, "prénom": "x", "nom": "y", "âge": 10}))
# session.commit()
- satıra bir kişi ekledikten sonra, 3. satırın yorum işaretini kaldırıyoruz. [session.commit] işlemi, altta yatan işlemi onaylayacak ve yeni bir işlem başlayacaktır. İşlem tamamlandıktan sonra, [personnes] tablosunun içeriği şu şekildedir:

[6]'te, ilk eklemenin korunduğu görülmektedir. Bunun nedeni, bu eklemenin 1 numaralı işlem içinde yapılmış olması ve ardından gelen hatanın 2 numaralı işlem içinde meydana gelmiş olmasıdır.
19.3. Komut Dosyaları 02: [sqlalchemy]'in eşlemeleri

02 komut dosyaları, 01 komut dosyalarının bir varyantıdır. [config.py] içinde mümkün olduğunca fazla yapılandırma yapmaya çalışıyoruz. Şimdi burada uygulamanın [sqlalchemy] ortamını yapılandırıyoruz:
def configure():
# yapılandırmadaki göreceli yolların mutlak referans yolu
root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"
# bağımlılıkların mutlak yolları
absolute_dependencies = [
# BaseEntity, MyException, Kişi, Yardımcı Programlar
f"{root_dir}/classes/02/entities",
]
# syspath ayarlanıyor
from myutils import set_syspath
set_syspath(absolute_dependencies)
# ithalatlar
from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData, UniqueConstraint
from sqlalchemy.orm import mapper, sessionmaker
# bir veritabanına bağlantı MySQL
engine = create_engine("mysql+mysqlconnector://admpersonnes:nobody@localhost/dbpersonnes")
# meta veriler
metadata = MetaData()
# tablo
personnes_table = Table("personnes", metadata,
Column('id', Integer, primary_key=True),
Column('prenom', String(30), nullable=False),
Column("nom", String(30), nullable=False),
Column("age", Integer, nullable=False),
UniqueConstraint('nom', 'prenom', name='uix_1')
)
# eşleme
from Personne import Personne
mapper(Personne, personnes_table, properties={
'id': personnes_table.c.id,
''ad': personnes_table.c.prenom,
'soyadı: personnes_table.c.nom,
'yaş: personnes_table.c.age
})
# oturum fabrikası
Session = sessionmaker()
Session.configure(bind=engine)
# bu bilgileri yapılandırma dosyasına ekliyoruz
config = {}
config["Session"] = Session
config["metadata"] = metadata
config["engine"] = engine
config["personnes_table"] = personnes_table
# sınıfların yapılandırılması
from Personne import Personne
Personne.excluded_keys = ['_sa_instance_state']
# yapılandırmayı oluşturuyoruz
return config
Açıklamalar
- 2-12. satırlar: Python Yolu yapılandırması;
- 14-45. satırlar: [sqlalchemy] ortamının yapılandırılması;
- 47-52. satırlar: [sqlalchemy] ortamı yapılandırma sözlüğüne eklenir;
- 54-56. satırlar: [Personne] sınıfı yapılandırılır;
Bu yapılandırma ile [main] komut dosyası şu hale gelir:
# uygulamayı yapılandırıyoruz
import config
config = config.configure()
# syspath yapılandırıldı - içe aktarmalar yapılır
from sqlalchemy.exc import IntegrityError, DatabaseError, InterfaceError
from sqlalchemy.orm.exc import FlushError
from Personne import Personne
session = None
try:
# bir oturum
session = config["Session"]()
# [personnes] tablosu siliniyor
session.execute("drop table if exists personnes")
# eşlemeden tablo yeniden oluşturuluyor
config["metadata"].create_all(config["engine"])
# iki ekleme
session.add(Personne().fromdict({"prénom": "x", "nom": "y", "âge": 10}))
personne = Personne().fromdict({"prénom": "x1", "nom": "y1", "âge": 7})
session.add(personne)
# iki eklemenin onaylanması
session.commit()
# bir sorgu
personnes = session.query(Personne).all()
# görüntüleme
print("Liste des personnes-----------")
for personne in personnes:
print(personne)
# ikinci eklenme işlemi başarısız olan iki eklenme işlemi daha
session.add(Personne().fromdict({"prénom": "x2", "nom": "y2", "âge": 10}))
session.add(Personne().fromdict({"prénom": "x2", "nom": "y2", "âge": 10}))
# bir sorgu
personnes = session.query(Personne).all()
# görüntüleme
print("Liste des personnes-----------")
for personne in personnes:
print(personne)
# oturum doğrulama
session.commit()
except (FlushError, DatabaseError, InterfaceError, IntegrityError) as erreur:
# görüntüleme
print(f"L'erreur suivante s'est produite : {erreur}")
# son oturumun iptali
if session:
print("rollback...")
session.rollback()
finally:
# görüntüleme
print("Travail terminé...")
# oturum kaynaklarını serbest bırakma
if session:
session.close()
Çalıştırma sonuçları şöyledir:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/databases/sqlalchemy/02/main.py
Liste des personnes-----------
{"âge": 10, "nom": "y", "prénom": "x", "id": 1}
{"âge": 7, "nom": "y1", "prénom": "x1", "id": 2}
L'erreur suivante s'est produite : (raised as a result of Query-invoked autoflush; consider using a session.no_autoflush block if this flush is occurring prematurely)
(mysql.connector.errors.IntegrityError) 1062 (23000): Duplicate entry 'y2-x2' for key 'uix_1'
[SQL: INSERT INTO personnes (prenom, nom, age) VALUES (%(prenom)s, %(nom)s, %(age)s)]
[parameters: {'prenom': 'x2', 'nom': 'y2', 'age': 10}]
(Background on this error at: http://sqlalche.me/e/13/gkpj)
rollback...
Travail terminé...
Process finished with exit code 0
phpMyAdmin'te, [personnes] tablosu şu hale gelmiştir:

Şimdi, [sqlalchemy] tarafından oluşturulan [personnes] tablosuna bakalım:

- [6]'te, farklı sütunlar için kullanılan türler;
- [7]'te, [id] sütununun [AUTO_INCREMENT] özniteliğine sahip olduğu görülüyor. Bu, tabloya bir satır eklendiğinde, bu satırın [id] sütununda bir değeri yoksa, bu değerin MySQL tarafından artımlı olarak (1, 2, 3, …) oluşturulacağı anlamına gelir: Bu özellik, tabloya bir kayıt eklerken birincil anahtar değeriyle uğraşmamızı gerektirmez: MySQL'in bunu oluşturmasına izin veririz;
- [8]'te, [id] sütununun birincil anahtar olduğu görülmektedir;
- [9]'te, [nom, prenom] alanları üzerinde teklik kısıtlaması bulunur;
19.4. Komut Dosyaları 03: [sqlalchemy] oturumundaki varlıkların işlenmesi

[config] yapılandırma dosyası, önceki örnektekiyle aynıdır. [main] komut dosyasında, [INSERT, UPDATE, DELETE, SELECT] yöntemleri kullanılarak [personnes] tablosu üzerinde klasik işlemler gerçekleştirilir:
# uygulama yapılandırılıyor
import config
config = config.configure()
# ithalatlar
from sqlalchemy import func
from sqlalchemy.exc import IntegrityError, DatabaseError, InterfaceError
from sqlalchemy.orm.session import Session
from Personne import Personne
# tablonun içeriğini görüntüler [personnes]
def affiche_table(session: Session):
print("----------------")
# bir sorgu
personnes = session.query(Personne).all()
# görüntüleme
affiche_personnes(personnes)
# kişi listesini görüntüler
def affiche_personnes(personnes: list):
print("----------------")
# görüntüleme
for personne in personnes:
print(personne)
# ana sayfa ---------------------------
session = None
try:
# bir oturum
session = config["Session"]()
# [personnes] tablosunun silinmesi
# checkfirst=True: önce tablonun varlığını kontrol eder
config["personnes_table"].drop(config["engine"], checkfirst=True)
# tablonun eşlemeden yeniden oluşturulması
config["metadata"].create_all(config["engine"])
# eklemeler
session.add(Personne().fromdict({"prénom": "Pierre", "nom": "Nicazou", "âge": 35}))
session.add(Personne().fromdict({"prénom": "Géraldine", "nom": "Colou", "âge": 26}))
session.add(Personne().fromdict({"prénom": "Paulette", "nom": "Girondé", "âge": 56}))
# oturumun içeriği görüntülenir
affiche_table(session)
# kişileri soyadlarına göre alfabetik sırayla ve aynı soyadlı kişileri ise adlarına göre alfabetik sırayla listeler
personnes = session.query(Personne).order_by(Personne.nom.desc(), Personne.prénom.desc())
# görüntüleme
affiche_personnes(personnes)
# [20,40] aralığındaki yaşa sahip kişilerin listesi, yaşa göre azalan sırada
# ardından, yaşı eşit olanlar soyadlarına göre alfabetik sırayla, soyadları eşit olanlar ise adlarına göre alfabetik sırayla
personnes = session.query(Personne). \
filter(Personne.âge >= 20, Personne.âge <= 40). \
order_by(Personne.âge.desc(), Personne.nom.asc(), Personne.prénom.asc())
# görüntüleme
affiche_personnes(personnes)
# Bayan Bruneau'nun eklenmesi
bruneau = Personne().fromdict({"prénom": "Josette", "nom": "Bruneau", "âge": 46})
session.add(bruneau)
# yaşının değiştirilmesi
bruneau.âge = 47
# soyadı Bruneau olan kişilerin listesi
personne = session.query(Personne).filter(func.lower(Personne.nom) == "bruneau").first()
# görüntüleme
affiche_personnes([personne])
# Bayan Bruneau'nun silinmesi
session.delete(personne)
# soyadı Bruneau olan kişilerin listesi
personnes = session.query(Personne).filter(func.lower(Personne.nom) == "bruneau")
# görüntüleme
affiche_personnes(personnes)
# oturumun onaylanması
session.commit()
except (DatabaseError, InterfaceError, IntegrityError) as erreur:
# görüntüleme
print(f"L'erreur suivante s'est produite : {erreur}")
# son oturumun iptali
if session:
session.rollback()
finally:
# görüntüleme
print("Travail terminé...")
# oturum kaynaklarını serbest bırakma
if session:
session.close()
Açıklamalar
- 20-25. satırlar: [affiche_personnes] işlevi, bir kişi listesindeki öğeleri görüntüler;
- 12-18. satırlar: [affiche_table] işlevi, [personnes] tablosunun içeriğini görüntüler;
- satır 34-36: [personnes] tablosu silinir. Önceki sürümlerin aksine, SQL komutu yerine [sqlalchemy] yöntemi kullanılır:
- config["personnes_table"], [personnes] tablosunu tanımlayan [Table] nesnesidir;
- config["engine"], [dbpersonnes] veritabanına bağlantı dizesidir;
- [checkfirst=True] adlı parametre, [personnes] tablosu mevcutsa işlemin gerçekleştirilmesini sağlar;
- 38-39. satırlar: [personnes] tablosu yeniden oluşturulur;
- satır 41-44: oturuma üç kişi eklenir. Bu kişilerin [personnes] tablosuna hemen eklenmeyeceği unutulmamalıdır. Bu, performansa odaklanan [sqlalchemy] stratejisine bağlıdır;
- satır 46-47: [personnes] tablosunun içeriği görüntülenir. Üç kişinin kayıtları henüz yapılmamışsa, bu istek nedeniyle şimdi eklenir;
- satır 49-50: Bir sorgunun sonuçlarını belirli bir sırayla sunmaya olanak tanıyan [order_by] yönteminin kullanımına dair bir örnek. [order_by(critère1, critère2)] sözdizimi, sonuçları önce [critère1] kriterine göre görüntüler; satırlar [critère1] değerinde eşleştiğinde ise [critère2] kriterine göre sıralanır. Bu şekilde birden fazla kriter belirleyebiliriz;
- 55-59. satırlar: [filter] yöntemi ile filtre kavramını tanıtmaktadır. [filter(critère1, critère2)] notasyonu, kullanılan kriterler arasında bir mantıksal ET (AND) oluşturur;
- 64-67. satırlar: yeni bir kullanıcı oturum açar;
- 70-71. satırlar: Filtrelenmiş bir sorguya başka bir örnek. [func.lower(param)] işlevi, [param]'i küçük harflerle döndürür. [func.xx] olarak gösterilen başka işlevler de mevcuttur. 71. satırdaki ifadede:
- [session.query.filter], [Personne] nesnelerinden oluşan bir liste döndürür;
- [session.query.filter.first], bu listenin ilk öğesini döndürür;
-
- satır: oturumdan bir öğe silinir;
-
- satır: oturum onaylanır;
Çalıştırma sonuçları şöyledir:
- 4-6. satırlar: oturumun içeriği;
- satır 8-10: oturum içeriği, isimlere göre azalan sırada;
- 12-13. satırlar: yaşı [20, 40] aralığında olan kişiler için oturum içeriği;
-
- satır: “bruneau” adlı kişi;
phpMyAdmin'te, yürütme sonunda [personnes] tablosunun içeriği şöyledir:

19.5. Komut Dosyaları 04: [PostgreSQL] veritabanının kullanımı

[04] dosyası, [03] dosyasının bir kopyasıdır. Tek bir değişiklik yapıyoruz: [config] dosyasındaki bağlantı dizesini değiştiriyoruz:
# bir veritabanına bağlantı PostgreSQL
engine = create_engine("postgresql+psycopg2://admpersonnes:nobody@localhost/dbpersonnes")
Artık bu bağlantı dizesi, SGBD ve [PostgreSQL] dosyalarının [dbpersonnes] veritabanını işaret etmektedir. [psycopg2] bağlayıcısının kullanıldığına dikkat edilmelidir. Bu bağlayıcının kurulu olması gerekir.
[main] komut dosyasının çalıştırılması aşağıdaki sonuçları verir:
[pgAdmin] aracıyla (bkz. |pgAdmin| paragrafı), [personnes] tablosu aşağıdaki durumdadır:

[personnes] tablosu, aşağıdaki SQL kodu ile oluşturulmuştur:

- [4-5]'te, [id] sütununun birincil anahtar olduğu görülmektedir. Ayrıca, bu sütunun [mot clé DEFAULT] varsayılan değeri olduğu da görülmektedir; bu sayede, birincil anahtar içermeyen bir satır eklendiğinde, bu anahtar SGBD tarafından oluşturulur. Bu yaygın bir işleyiştir: birincil anahtarların oluşturulmasını SGBD'e bırakırız;
[sqlalchemy] komut dosyalarının bu 05 sürümü, bir SGBD'ten diğerine geçişin ne kadar kolay olduğunu açıkça göstermektedir: bir yapılandırma komut dosyasında bağlantı dizesini değiştirmek yeterli olmuştur. Başka hiçbir şey değişmedi. Yukarıdaki [id, nom, prenom, age] sütun türlerini, |02| örneğindeki MySQL tablosundaki sütun türleriyle karşılaştırırsak, bunların farklı olduğunu görürüz. [sqlalchemy], bunları kullanılan SGBD'e uyarlar. Yeni bir SGBD'e bu kadar kolay uyum sağlanabilmesi, [sqlalchemy]'i veya başka bir ORM'i benimsemek için yeterli bir nedendir.
19.6. Komut Dosyaları 05: Tam örnek

İncelenen örnek, |troiscouches-v01| paragrafında incelenen örneğin bir versiyonudur. Bu örnek, [ui, métier, dao] varlıklarını işleyen üç katmanlı bir mimariyi [Classe, Elève, Matière, Note] sunuyordu. Varlıklar, bir [dao] katmanında sabit olarak kodlanmıştı. Şimdi bunları bir veritabanına aktarıyoruz. İki SGBD kullanacağız: MySQL ve PostgreSQL.
19.6.1. Uygulamanın mimarisi
Uygulamanın mimarisi şu şekilde olacaktır:

- [1-3]'te, |troiscouches-v01| örneğinde zaten mevcut olan [ui, métier, dao] katmanları bulunur. [dao] katmanı artık [ORM] katmanıyla iletişim kurmaktadır;
- [1-5] katmanları Python kodu ile uygulanmaktadır;
19.6.2. Veritabanları
MySQL adlı bir veritabanı oluşturuyoruz; bu veritabanının adı [dbecole], sahibi [admecole] ve şifresi [mdpecole]'tir. Bunun için |création d'une base de données| paragrafında açıklanan prosedürü izliyoruz:


- [1]'te, [dbecole] veritabanı, [3] tabloları hariç;
- [7] veritabanında, [admecole] kullanıcısı bu veritabanı üzerinde tüm ayrıcalıklara sahiptir;
Aynı işlemi SGBD ve PostgreSQL için de yapıyoruz. [dbecole] adlı bir veritabanı oluşturuyoruz; bu veritabanının sahibi [admecole] kullanıcısı ve şifresi [mdpecole]'tir. Bunun için |création d'une base de données| paragrafında açıklanan prosedürü izliyoruz:

- [1]'te, [dbecole] veritabanı;
- [2]'te, [admecole] kullanıcısı;
- [3-4]'e, [dbecole] veritabanı [admecole] kullanıcısına aittir;
19.6.3. Uygulama tarafından işlenen varlıklar
|troiscouches v01| uygulamasında işlenen varlıklar şunlardı (bkz. |entités|). Bu varlıklar, yukarıdaki veritabanlarında depolanacaktır. Yeni uygulamada bu varlıkları çoğaltmayacağız. Bunları, halihazırda tanımlandıkları yerden alacağız.
[Classe] sınıfı:
# içe aktarmalar
from BaseEntity import BaseEntity
from MyException import MyException
from Utils import Utils
class Classe(BaseEntity):
# sınıf durumundan hariç tutulan öznitelikler
excluded_keys = []
# sınıf özellikleri
@staticmethod
def get_allowed_keys() -> list:
# id: sınıf kimliği
# ad: sınıf adı
return BaseEntity.get_allowed_keys() + ["nom"]
# getter
@property
def nom(self: object) -> str:
return self.__nom
# setter'lar
@nom.setter
def nom(self: object, nom: str):
# ad, boş olmayan bir karakter dizisi olmalıdır
if Utils.is_string_ok(nom):
self.__nom = nom
else:
raise MyException(11, f"Le nom de la classe {self.id} doit être une chaîne de caractères non vide")
[Elève] sınıfı:
# ithalatlar
from BaseEntity import BaseEntity
from Classe import Classe
from MyException import MyException
from Utils import Utils
class Elève(BaseEntity):
# sınıf durumundan hariç tutulan özellikler
excluded_keys = []
# sınıf özellikleri
@staticmethod
def get_allowed_keys() -> list:
# id: öğrencinin kimlik numarası
# soyadı: öğrencinin soyadı
# ad: öğrencinin adı
# sınıf: öğrencinin sınıfı
return BaseEntity.get_allowed_keys() + ["nom", "prénom", "classe"]
# alıcılar
@property
def nom(self: object) -> str:
return self.__nom
@property
def prénom(self: object) -> str:
return self.__prénom
@property
def classe(self: object) -> Classe:
return self.__classe
# ayarlayıcılar
@nom.setter
def nom(self: object, nom: str) -> str:
# soyadı, boş olmayan bir karakter dizisi olmalıdır
if Utils.is_string_ok(nom):
self.__nom = nom
else:
raise MyException(41, f"Le nom de l'élève {self.id} doit être une chaîne de caractères non vide")
@prénom.setter
def prénom(self: object, prénom: str) -> str:
# ad, boş olmayan bir karakter dizisi olmalıdır
if Utils.is_string_ok(prénom):
self.__prénom = prénom
else:
raise MyException(42, f"Le prénom de l'élève {self.id} doit être une chaîne de caractères non vide")
@classe.setter
def classe(self: object, value):
try:
# Sınıf türü bekleniyor
if isinstance(value, Classe):
self.__classe = value
# veya bir dict türü
elif isinstance(value,dict):
self.__classe=Classe().fromdict(value)
# veya bir json türü
elif isinstance(value,str):
self.__classe = Classe().fromjson(value)
except BaseException as erreur:
raise MyException(43, f"L'attribut [{value}] de l'élève {self.id} doit être de type Classe ou dict ou json. Erreur : {erreur}")
[Matière] sınıfı:
# ithalatlar
from BaseEntity import BaseEntity
from MyException import MyException
from Utils import Utils
class Matière(BaseEntity):
# sınıf durumundan hariç tutulan öznitelikler
excluded_keys = []
# sınıf özellikleri
@staticmethod
def get_allowed_keys() -> list:
# id: ders kimliği
# ad: dersin adı
# katsayı: dersin katsayısı
return BaseEntity.get_allowed_keys() + ["nom", "coefficient"]
# getter
@property
def nom(self: object) -> str:
return self.__nom
@property
def coefficient(self: object) -> float:
return self.__coefficient
# ayarlayıcılar
@nom.setter
def nom(self: object, nom: str):
# ad, boş olmayan bir karakter dizisi olmalıdır
if Utils.is_string_ok(nom):
self.__nom = nom
else:
raise MyException(21, f"Le nom de la matière {self.id} doit être une chaîne de caractères non vide")
@coefficient.setter
def coefficient(self, coefficient: float):
# katsayı, 0'dan büyük bir gerçek sayı olmalıdır
erreur = False
if isinstance(coefficient, (int, float)):
if coefficient >= 0:
self.__coefficient = coefficient
else:
erreur = True
else:
erreur = True
# hata mı?
if erreur:
raise MyException(22, f"Le coefficient de la matière {self.nom} doit être un réel >=0")
[Note] sınıfı:
# ithalatlar
from BaseEntity import BaseEntity
from Elève import Elève
from Matière import Matière
from MyException import MyException
class Note(BaseEntity):
# sınıf durumundan hariç tutulan öznitelikler
excluded_keys = []
# sınıf özellikleri
@staticmethod
def get_allowed_keys() -> list:
# id: notun kimliği
# değer: notun kendisi
# öğrenci: notla ilgili öğrenci (Öğrenci türü)
# ders: notla ilgili ders (Ders türü)
# Dolayısıyla Not nesnesi, bir öğrencinin bir derste aldığı nottur
return BaseEntity.get_allowed_keys() + ["valeur", "élève", "matière"]
# alıcılar
@property
def valeur(self: object) -> float:
return self.__valeur
@property
def élève(self: object) -> Elève:
return self.__élève
@property
def matière(self: object) -> Matière:
return self.__matière
# getter'lar
@valeur.setter
def valeur(self: object, valeur: float):
# not, 0 ile 20 arasında bir gerçek sayı olmalıdır
if isinstance(valeur, (int, float)) and 0 <= valeur <= 20:
self.__valeur = valeur
else:
raise MyException(31,
f"L'attribut {valeur} de la note {self.id} doit être un nombre dans l'intervalle [0,20]")
@élève.setter
def élève(self: object, value):
try:
# "Öğrenci" türü beklenir
if isinstance(value, Elève):
self.__élève = value
# veya "dict" türü
elif isinstance(value, dict):
self.__élève = Elève().fromdict(value)
# veya json türü
elif isinstance(value, str):
self.__élève = Elève().fromjson(value)
except BaseException as erreur:
raise MyException(32,
f"L'attribut [{value}] de la note {self.id} doit être de type Elève ou dict ou json. Erreur : {erreur}")
@matière.setter
def matière(self: object, value):
try:
# "Matière" türü bekleniyor
if isinstance(value, Matière):
self.__matière = value
# veya dict türü
elif isinstance(value, dict):
self.__matière = Matière().fromdict(value)
# veya bir json türü
elif isinstance(value, str):
self.__matière = Matière().fromjson(value)
except BaseException as erreur:
raise MyException(33,
f"L'attribut [{value}] de la note {self.id} doit être de type Matière ou dict ou json. Erreur : {erreur}")
19.6.4. Yapılandırma

Yapılandırma birkaç dosyaya bölünmüştür:
- genel yapılandırma [config.py] dosyasında yer almaktadır: bu dosya, uygulamanın Python yolunu belirler ve mimarinin katmanlarını oluşturur;
- [sqlalchemy] dosyasındaki yapılandırma, [config_database] dosyasında yer alır: Sınıflar ve Tablolar arasındaki eşlemeleri yapar;
- uygulamanın katmanları [config_layers] dosyasında yapılandırılmıştır;
[config] dosyası şu şekildedir:
def configure(config: dict) -> dict:
import os
# 1. adım ---
# uygulamanın Python yolunu ayarlıyoruz
# bu komut dosyasının bulunduğu klasörün mutlak yolu
script_dir = os.path.dirname(os.path.abspath(__file__))
# yapılandırmadaki göreceli yolların referans aldığı mutlak yol
root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"
# bağımlılıkların mutlak yolları
absolute_dependencies = [
# BaseEntity, MyException
f"{root_dir}/classes/02/entities",
# üç katmanlı proje v01
f"{root_dir}/troiscouches/v01/interfaces",
f"{root_dir}/troiscouches/v01/services",
f"{root_dir}/troiscouches/v01/entities",
# bu projenin dosyaları
script_dir,
f"{script_dir}/../services",
]
# syspath güncellemesi
from myutils import set_syspath
set_syspath(absolute_dependencies)
# 2. adım ------
# veritabanı yapılandırması
import config_database
config = config_database.configure(config)
# 3. adım ------
# uygulama katmanlarının örneklenmesi
import config_layers
config = config_layers.configure(config)
# yapılandırmayı geri yükleme
return config
- 4-27. satırlar: uygulamanın Python Yolu'nun oluşturulması;
- 29-32. satırlar: [sqlalchemy] yapılandırması;
- 34-37. satırlar: uygulamanın katmanlarının yapılandırılması;
[config_database] dosyası şöyledir:
def configure(config: dict) -> dict:
# ['sgbd'] yapılandırması, kullanılan SGBD'in adıdır
# mysql: MySQL
# pgres: PostgreSQL
# SQLAlchemy yapılandırması
from sqlalchemy import Table, Column, Integer, MetaData, String, Float, ForeignKey, create_engine
from sqlalchemy.orm import mapper, relationship, sessionmaker
# kullanılan veritabanlarına bağlantı dizeleri
engines = {
'mysql': "mysql+mysqlconnector://admecole:mdpecole@localhost/dbecole",
'pgres': "postgresql+psycopg2://admecole:mdpecole@localhost/dbecole"
}
# kullanılan veritabanına bağlantı dizesi
engine = create_engine(engines[config['sgbd']])
# meta veriler
metadata = MetaData()
# veritabanındaki tablolar
tables = {}
# eşleştirilmiş sınıflar
from Classe import Classe
from Elève import Elève
from Note import Note
from Matière import Matière
# sınıf tablosu
tables['classes'] = classes_table = \
Table("classes", metadata,
Column('id', Integer, primary_key=True),
Column('nom', String(30), nullable=False),
)
mapper(Classe, tables['classes'], properties={
'id': classes_table.c.id,
'ad': classes_table.c.nom
})
# öğrenci tablosu
tables['élèves'] = élèves_table = \
Table("élèves", metadata,
Column('id', Integer, primary_key=True),
Column('nom', String(30), nullable=False),
Column('prénom', String(30), nullable=False),
# bir öğrenci bir sınıfa aittir
Column('classe_id', Integer, ForeignKey('classes.id')),
)
# eşleme
mapper(Elève, tables['élèves'], properties={
'id': élèves_table.c.id,
'soyadı: élèves_table.c.nom,
'soyadı': élèves_table.c.prénom,
'sınıf: relationship(Sınıf, backref="öğrenciler", lazy="select")
})
# içindekiler
tables['matières'] = matières_table = \
Table("matières", metadata,
Column('id', Integer, primary_key=True),
Column('nom', String(30), nullable=False),
Column('coefficient', Float, nullable=False)
)
# eşleme
mapper(Matière, tables['matières'], properties={
'id': matières_table.c.id,
'ad': matières_table.c.nom,
"coefficient": matières_table.c.coefficient
})
# not tablosu
tables['notes'] = notes_table = \
Table("notes", metadata,
Column('id', Integer, primary_key=True),
Column('valeur', Float, nullable=False),
# bir not, bir öğrencinin notudur
Column('élève_id', Integer, ForeignKey('élèves.id')),
# bir not, bir dersin notudur
Column('matière_id', Integer, ForeignKey('matières.id')),
)
# eşleme
mapper(Note, tables['notes'], properties={
'id': notes_table.c.id,
'değer': notes_table.c.valeur,
'öğrenci': ilişki(Öğrenci, backref="notlar", lazy="seç"),
'ders': ilişki(Ders, backref="notlar", lazy="seç")
})
# varlık yapılandırması [BaseEntity]
Elève.excluded_keys = ['_sa_instance_state', 'notes', 'classe']
Classe.excluded_keys = ['_sa_instance_state', 'élèves']
Matière.excluded_keys = ['_sa_instance_state', 'notes']
Note.excluded_keys = ['_sa_instance_state', 'matière', 'élève']
# oturum fabrikası
Session = sessionmaker()
Session.configure(bind=engine)
# bir oturum
session = Session()
# bazı bilgiler yapılandırma sözlüğüne kaydedilir
config['database'] = {"engine": engine, "metadata": metadata, "tables": tables, "session": session}
# yapılandırma uygulanır
return config
Açıklamalar
- satır 1-4: [configure] işlevi, parametre olarak bir sözlük alır. Yalnızca [sgbd] anahtarı kullanılır. Veritabanı MySQL ise bu anahtarın değeri [mysql], veritabanı PostgreSQL ise [pgres] olur;
- 6-9. satırlar: [sqlalchemy]'ten öğelerin içe aktarılması. [config_database] komut dosyası, [dbecole] veritabanındaki tablolar ile [Classes, Elève, Matière, Note] varlıklarını eşler. Tabloda, varlık verileri bir satırda kapsüllenmiştir. Python kodunda ise bu veriler bir nesne içinde toplanır. İşte bu nedenle ORM (Nesne-İlişkisel Eşleştirici) adı verilmiştir: ORM, ilişkisel bir veritabanındaki satırlar ile nesneler arasında bir eşleme (bağlantı) kurar. Bu uygulamada, dört [Classe, Elève, Matière, Note] varlık var ve bunlar dört [classes, élèves, matières, notes] tablosuna bağlanacak. Tabloların adlarında aksanlı karakterler bulunabileceğini unutmayın;
- 11-17. satırlar: Kullanılan veritabanına bağlantı dizesi. Bu, config[‘sgbd’] öğesine bağlıdır;
- satır 24-28: [sqlalchemy] eşlemesine tabi tutulacak uygulama varlıkları. Bu satırlar yürütüldüğünde, Python Yolu [config] betiği tarafından önceden oluşturulmuş olacaktır;
- 30-40. satırlar: [Classe] varlığı ile [classes] tablosu arasındaki eşleme;
- 30-35. satırlar: [classes] tablosu, [sqlalchemy]'in [Table] sınıfıyla tanımlanmıştır. Bu tablonun iki sütunu olduğunu belirtiriz:
- birincil anahtar olan ve sınıf numarasını içeren [id] sütunu, satır 33;
- [nom] sütunu, sınıf adını içerir (34. satır);
- 31-32. satırlar: Python'da x=y=z sözdiziminin geçerli olduğunu unutmayın: z'nin değeri y'ye atanır, ardından y'nin değeri x'e atanır;
- satır 37-40: [classes] tablosunun sütunları ile [Classe] varlığının özellikleri arasındaki eşleşmeler listelenir;
- satır 42-57: [Elève] varlığı ile [élèves] tablosu arasındaki eşleme;
- satır 51-57: [élèves] tablosu, [sqlalchemy]'in [Table] sınıfı ile tanımlanmıştır. Bu tablonun dört sütunu olduğunu belirtiriz:
- birincil anahtar olan ve öğrenci numarasını içeren [id] sütunu, satır 45;
- [nom] sütunu, öğrencinin soyadını içerir (46. satır);
- [prénom] sütunu, öğrencinin adını içerir (47. satır). Sütun adlarında aksanlı karakterler bulunabileceğini unutmayın;
-
- satırda, öğrencinin ait olduğu sınıf numarasını içeren [classe_id] sütunu. Buna yabancı anahtar denir. [élèves.classe_id], [classes.id] sütunundaki bir yabancı anahtardır (ForeignKey). Bu, [élèves.classe_id] değerinin [classes.id] sütununda mevcut olması gerektiği anlamına gelir;
- 51-57. satırlar: [élèves] tablosunun sütunları ile [Elève] varlığının özellikleri arasındaki eşleşmeler listelenmiştir:
- 53-55. satırlar kolayca anlaşılabilir;
-
- satır ise daha karmaşıktır: bu satır, [Elève.classe] özelliğinin değerinin, [élèves] ve [classes] tablolarını birbirine bağlayan yabancı anahtar ilişkisi (relationship) aracılığıyla hesaplandığını tanımlar. [relationship] işlevinin parametreleri şunlardır:
- [Classe]: [Elève] varlığının yabancı anahtar ilişkisi kurduğu varlığın adıdır. Bu ilişki, [classes] tablosunda bulunan bir yabancı anahtarın varlığıyla [élèves] tablosunda somutlaşmalıdır. Bu yabancı anahtarın var olduğunu biliyoruz;
- [backref="élèves"]: [Classe] varlığına eklenecek bir özelliğin adı. [Classe.élèves], sınıftaki tüm öğrencilerin listesi olacaktır. Bu özellik daha önce mevcut olmamalıdır. Zaten mevcutsa, burada [backref] için başka bir ad seçmek yeterlidir. Geliştiricinin bu özelliği yönetmesi gerekmez. Bunu [sqlalchemy] yapacaktır. Geliştirici sadece bu özelliğin [sqlalchemy] tarafından eklendiğini ve kodunda kullanabileceğini bilmelidir;
- [lazy=’select’]: Bu, ORM’in [Elève.classe] özelliğine hemen bir değer atamaya çalışmaması gerektiği anlamına gelir. Bu özelliği, yalnızca kod açıkça talep ettiğinde aramalıdır. Dolayısıyla:
- kod tüm öğrencilerin listesini isterse, öğrenciler listelenir ancak [classe] özelliği hesaplanmaz;
- biraz sonra, kod belirli bir öğrenci olan [e] ile ilgilenir ve onun sınıfı olan [e.classe]'e başvurur. Bu referans, [sqlalchemy]'in öğrencinin sınıfını getirmek için veritabanına bir sorgu yapmasını zorlayacaktır; bu işlem, geliştirici için şeffaf bir şekilde gerçekleşir;
- ayrıca [lazy=’select’], veritabanına gereksiz sorguların yapılmasını önlemeyi amaçlamaktadır;
-
- satır: ORM, [élèves] tablosundan bir satır aldığında, [id, nom, prénom, classe_id] bilgilerini alır. Bundan sonra, bir Elève nesnesi (id, soyadı, adı, sınıf) oluşturmalıdır. [id, nom, prénom] özellikleri için bu bir sorun teşkil etmez. Ancak [classe] özelliği için durum daha karmaşıktır. Bu özelliğin değeri, [Classe] türünde bir nesne referansıdır. Oysa ORM nesnesi yalnızca [élèves.classe_id] bilgisine sahiptir. [élèves.classe_id], [classes.id] sütununda bir yabancı anahtar olduğundan, burada bu ilişkiyi kullanarak [classes] tablosundan id=[élèves.classe_id] numaralı satırı (bu satır mutlaka mevcuttur) ve bu satırdan [Elève.classe] özelliğinin beklediği [Classe] nesnesini oluşturması istenir;
- satır 59-71: [Matière] varlığı ile [matières] tablosu arasındaki eşleme;
- satır 59-65: [matières] olarak adlandırılan [sqlalchemy] tablosunun tanımı;
- satır 66-71: [matières] tablosunun sütunları ile [Matière] varlığının özellikleri arasındaki eşleşmeler listelenmiştir. Burada herhangi bir zorluk yoktur;
- 73-90. satırlar: [Note] varlığı ile [notes] tablosu arasındaki eşleme;
- 73-82. satırlar: [notes] olarak adlandırılan [sqlalchemy] tablosunun tanımı. Bu tablonun iki yabancı anahtarı vardır:
-
- satırda, [notes.élève_id] sütunu değerlerini [élèves.id] sütunundan alır]. Bu yabancı anahtar, bir notun belirli bir öğrenciye ait olduğunu ifade eder;
-
- satır, [notes.matière_id] sütunu değerlerini [matières.id] sütunundan alır. Bu yabancı anahtar, bir notun belirli bir dersin notu olduğunu ifade eder;
-
- satır 84-90: [Note] varlığı ile [notes] tablosu arasındaki eşleme:
- satır 88: [Note.élève] özelliğinin değeri, [Elève] türünde bir örnek olmalıdır. ORM'in, [notes] tablosundaki satırında yer alan tek bilgi, [élèves.id] sütununa referans veren [notes.élève_id] sütunudur. Burada, notu elimizde olan [Elève] örneğini bulmak için bu yabancı anahtar ilişkisini kullanmamız gerektiği belirtilmektedir. Ayrıca, [relationship(Elève, backref="notes", …)], öğrencinin notlarının listesi olacak yeni [Elève.notes] özelliğini oluşturacaktır. Bu özellik, [Elève] sınıfında önceden mevcut olmamalıdır;
-
- satır: [Note.matière] özelliğinin değeri, [Matière] türünde bir örnek olmalıdır. ORM'in, [notes] tablosundaki satırında, [matières.id] sütununa referans veren [notes.matière_id] sütunu dışında başka bir bilgi bulunmamaktadır. Burada, notu bulunan [Matière] örneğini bulmak için bu yabancı anahtar ilişkisini kullanmamız gerektiği belirtilmektedir. Ayrıca, [relationship(Matière, backref="notes", …)], dersin not listesini içeren yeni [Matière.notes] özelliğini oluşturacaktır. Bu özellik, [Matière] sınıfında önceden mevcut olmamalıdır;
- 92-96. satırlar: [BaseEntity]’ten türetilen her varlık için, varlık özellik sözlüğünden (BaseEntity.asdict) hariç tutulacak özelliklerin listesi tanımlanır. [sqlalchemy]'in, eşlenen tüm varlıklara [_sa_instance_state] özelliğini eklediğini görmüştük. Bu özelliğin özellik sözlüğünde yer almasını istemiyoruz. Ayrıca, önceki eşlemelerin varlıklara yeni özellikler eklediğini de görmüştük:
- [Elève.notes]: öğrencinin tüm notları;
- [Classe.élèves]: sınıfın tüm öğrencileri;
- [Matière.notes]: dersin tüm notları;
Genel olarak, bu özelliklerin varlık durumuna eklenmesini istemeyiz. Zira, bunların değerini hesaplamanın bir maliyeti vardır ve bu değer çoğu zaman gereksizdir. Dolayısıyla, adı ‘X’ olan öğrenciyi alırsak:
- (devamı)
- ORM, bir [Elève(id, nom, prénom, classe, notes)] varlığını döndürecektir. [lazy=’select’] nedeniyle, veritabanındaki yabancı anahtarlarla ilişkili [classe, notes] özellikleri hesaplanmayacaktır;
- şimdi bu öğrencinin jSON dizisini görüntülersem, bunun varlığın [asdict] sözlüğündeki jSON dizisi olacağı bilinir. [classe] ve [notes] özellikleri içindeyse, [sqlalchemy], bunların değerlerini hesaplamak için SQL sorguları yapmak zorunda kalacaktır. Bu, maliyetlidir. Bu sorguları önleyebiliyorsak, bu daha iyidir;
- burada, yabancı anahtarla ilişkili tüm özellikleri hariç tuttuk;
- 98-100. satırlar: bir [Session factory] nesnesinin oluşturulması ve yapılandırılması (factory=üretim fabrikası). [Session] nesnesi, işlemlere dayalı [sqlalchemy] oturumları oluşturmak için kullanılır;
- satır 102-103: bir sqlalchemy oturumu oluşturulur;
- satır 106: [sqlalchemy] yapılandırmasının bazı öğeleri, uygulamanın genel yapılandırma sözlüğüne eklenir;
- satır 109: bu sözlük geri verilir;
[config_layers] dosyası, uygulamanın katmanlarını yapılandırır:
def configure(config: dict) -> dict:
# [dao] katmanının örneklenmesi
from DatabaseDao import DatabaseDao
dao = DatabaseDao(config)
# [métier] katmanının örneklenmesi
from Métier import Métier
métier = Métier(dao)
# [ui] katmanının örneklenmesi
from Console import Console
ui = Console(métier)
# katmanlar yapılandırmaya eklenir
config['dao'] = dao
config['métier'] = métier
config['ui'] = ui
# yapılandırma geri yükleniyor
return config
-
- satır: [configure] işlevi, uygulamanın genel yapılandırma sözlüğünü alır;
- 2-12. satırlar: Uygulamanın katmanları oluşturulur;
- satır 15-17: katman referansları genel yapılandırmaya eklenir;
-
- satır: yeni yapılandırma döndürülür;
19.6.5. [dao] katmanı - 1

Burada, [dao] [3] katmanınınönceki paragrafta açıklandığı şekilde yapılandırılmış ORM, [sqlalchemy] ve [4] katmanlarıyla iletişim kurduğunu anlamak gerekir. |troiscouches v01| uygulamasının üç katmanından ([ui, métier, dao]) yalnızca [dao] katmanı yeniden yazılmalıdır. [ui, métier] katmanları korunur.
[dao] katmanının uygulaması, [services] klasörüne yerleştirilmiştir:

[InterfaceDatabaseDao], [dao] katmanının arayüzüdür:
from abc import ABC, abstractmethod
from InterfaceDao import InterfaceDao
class InterfaceDatabaseDao(InterfaceDao, ABC):
# veritabanının başlatılması
@abstractmethod
def init_database(self, data: dict):
pass
-
- satır: [InterfaceDatabaseDao] arayüzü, hem [ABC] sınıfından soyut bir sınıf olarak hem de |troiscouches v01| projesindeki [InterfaceDao] arayüzünden türetilmiştir;
- 8-11. satırlar: [InterfaceDao]'ten miras alınan yöntemlere [init_database] yöntemi eklenir. Bu yöntemin görevi, 10. satırda parametre olarak kendisine aktarılan [data] sözlüğündeki verilerle veritabanını başlatmaktır;
[InterfaceDao] arayüzünün şu şekilde olduğunu hatırlayalım:
# içe aktarmalar
from abc import ABC, abstractmethod
# DAO arayüzü
from Elève import Elève
class InterfaceDao(ABC):
# sınıf listesi
@abstractmethod
def get_classes(self: object) -> list:
pass
# öğrenci listesi
@abstractmethod
def get_élèves(self: object) -> list:
pass
# ders listesi
@abstractmethod
def get_matières(self: object) -> list:
pass
# not listesi
@abstractmethod
def get_notes(self: object) -> list:
pass
# bir öğrencinin not listesi
@abstractmethod
def get_notes_for_élève_by_id(self: object, élève_id: int) -> list:
pass
# bir öğrenciyi kimlik numarasına göre arama
@abstractmethod
def get_élève_by_id(self: object, élève_id: int) -> Elève:
pass
[dao] katmanının uygulaması şu şekildedir:
from sqlalchemy.exc import DatabaseError, IntegrityError, InterfaceError
from Classe import Classe
from Elève import Elève
from InterfaceDatabaseDao import InterfaceDatabaseDao
from Matière import Matière
from MyException import MyException
from Note import Note
class DatabaseDao(InterfaceDatabaseDao):
def __init__(self, config: dict):
# veritabanı = {"motor": motor, "meta veri": meta veri, "tablolar": tablolar, "oturum": oturum}
self.database = config['database']
self.session = self.database['session']
def init_database(self, data: dict):
…
…
-
- satır: [DatabaseDao] sınıfı, [InterfaceDatabaseDao] arayüzünü uygular;
- 13-16. satırlar: Sınıfın oluşturucusu. Parametre olarak uygulama yapılandırma sözlüğünü alır;
-
- satır: [sqlalchemy] yapılandırması kaydedilir;
- satır 16: veritabanını yönetmek için kullanılacak [sqlalchemy] oturumu kaydedilir;
- satır 18: [init_database] yöntemi, veritabanını [data] sözlüğüyle başlatır;
[data] sözlüğü, aşağıdaki [data.py] komut dosyası ile uygulanır:
def configure():
from Classe import Classe
from Elève import Elève
from Matière import Matière
from Note import Note
# sınıfları başlatıyoruz
classe1 = Classe().fromdict({"id": 1, "nom": "classe1"})
classe2 = Classe().fromdict({"id": 2, "nom": "classe2"})
classes = [classe1, classe2]
# dersler
matière1 = Matière().fromdict({"id": 1, "nom": "matière1", "coefficient": 1})
matière2 = Matière().fromdict({"id": 2, "nom": "matière2", "coefficient": 2})
matières = [matière1, matière2]
# öğrenciler
élève11 = Elève().fromdict({"id": 11, "nom": "nom1", "prénom": "prénom1", "classe": classe1})
élève21 = Elève().fromdict({"id": 21, "nom": "nom2", "prénom": "prénom2", "classe": classe1})
élève32 = Elève().fromdict({"id": 32, "nom": "nom3", "prénom": "prénom3", "classe": classe2})
élève42 = Elève().fromdict({"id": 42, "nom": "nom4", "prénom": "prénom4", "classe": classe2})
élèves = [élève11, élève21, élève32, élève42]
# farklı derslerdeki öğrenci notları
note1 = Note().fromdict({"id": 1, "valeur": 10, "élève": élève11, "matière": matière1})
note2 = Note().fromdict({"id": 2, "valeur": 12, "élève": élève21, "matière": matière1})
note3 = Note().fromdict({"id": 3, "valeur": 14, "élève": élève32, "matière": matière1})
note4 = Note().fromdict({"id": 4, "valeur": 16, "élève": élève42, "matière": matière1})
note5 = Note().fromdict({"id": 5, "valeur": 6, "élève": élève11, "matière": matière2})
note6 = Note().fromdict({"id": 6, "valeur": 8, "élève": élève21, "matière": matière2})
note7 = Note().fromdict({"id": 7, "valeur": 10, "élève": élève32, "matière": matière2})
note8 = Note().fromdict({"id": 8, "valeur": 12, "élève": élève42, "matière": matière2})
notes = [note1, note2, note3, note4, note5, note6, note7, note8]
# tüm veriler bir araya getirilir
data = {"élèves": élèves, "classes": classes, "matières": matières, "notes": notes}
# veriler
return data
-
- satır: [init_database] yöntemine aktarılacak sözlük. Bu sözlük aşağıdaki anahtarlardan oluşur (32. satır):
- [élèves]: öğrenci listesi;
- [classes]: sınıf listesi;
- [matières]: ders listesi;
- [notes]: tüm öğrencilerin tüm derslerdeki notlarının listesi;
Şimdi [init_database] yöntemine geri dönelim:
def init_database(self, data: dict):
# veritabanı yapılandırması
database = self.database
engine = database['engine']
metadata = database['metadata']
tables = database['tables']
try:
# mevcut tabloların silinmesi
# checkfirst=True: önce tablonun varlığını kontrol eder
tables["notes"].drop(engine, checkfirst=True)
tables["matières"].drop(engine, checkfirst=True)
tables["élèves"].drop(engine, checkfirst=True)
tables["classes"].drop(engine, checkfirst=True)
# eşlemeden tabloların yeniden oluşturulması
metadata.create_all(engine)
# tabloların doldurulması
session = self.session
# sınıflar
classes = data["classes"]
for classe in classes:
session.add(classe)
# dersler
matières = data["matières"]
for matière in matières:
session.add(matière)
# öğrenciler
élèves = data["élèves"]
for élève in élèves:
session.add(élève)
# notlar
notes = data["notes"]
for note in notes:
session.add(note)
# işleme
session.commit()
except (DatabaseError, InterfaceError, IntegrityError) as erreur:
# oturumun iptali
if session:
session.rollback()
# istisna iletiliyor
raise MyException(23, f"{erreur}")
- 3-6. satırlar: veritabanı yapılandırmasından bilgiler alınır;
- 9-14. satırlar: [sqlalchemy] yapılandırmasının dört varlığı dört [élèves, matières, classes, notes] tablosuna eşlediğini görmüştük. Varsa bu tabloları silerek başlıyoruz;
- 16-17. satırlar: Az önce sildiğimiz dört tabloyu yeniden oluşturuyoruz;
- 22-25. satırlar: Tüm sınıfları oturuma ekliyoruz;
- 27-30. satırlar: Tüm dersleri oturuma ekliyoruz;
- satır 32-35: tüm öğrencileri oturuma ekliyoruz;
- satır 37-40: tüm notları oturuma ekliyoruz;
- bu eklemeleri yapmak için belirli bir sıra izledik. Önce diğer varlıklarla ilişkisi olmayan varlıklarla başladık, ilişkisi olanlarla bitirdik. Böylece öğrencileri oturuma eklediğimizde, bunların referans verdiği sınıflar zaten oturumda yer almaktadır;
- satır 43: [sqlalchemy] oturumu onaylanır. Bu işlemden sonra, oturumdaki tüm verilerin veritabanıyla senkronize edildiğinden emin oluruz. Başka bir deyişle, veriler tablolara aktarılmıştır. Bu, [sqlalchemy] yapılandırmasında yapılan eşlemeler sayesinde gerçekleştirilebildi. [sqlalchemy], her bir varlığın tablolarda nasıl depolanması gerektiğini bilir. [sqlalchemy] ayrıca tabloların sahip olabileceği yabancı anahtarları da oluşturmuştur;
- 44-49. satırlar: Bir sorunla karşılaşılırsa, [sqlalchemy] oturumu iptal edilir ve 49. satırda bir istisna atılır;
19.6.6. Veritabanının başlatılması

[main_init_database] komut dosyası, [data.py] komut dosyasının içeriğiyle veritabanını başlatır. Kod şöyledir:
# mysql veya pgres parametresi bekleniyor
import sys
syntaxe = f"{sys.argv[0]} mysql / pgres"
erreur = len(sys.argv) != 2
if not erreur:
sgbd = sys.argv[1].lower()
erreur = sgbd != "mysql" and sgbd != "pgres"
if erreur:
print(f"syntaxe : {syntaxe}")
sys.exit()
# uygulama yapılandırılıyor
import config
config = config.configure({'sgbd': sgbd})
# syspath yapılandırıldı - içe aktarmalar yapılabilir
from MyException import MyException
# veritabanına eklenecek veriler alınıyor
import data
data = data.configure()
# katman alınıyor [dao]
dao = config["dao"]
# ----------- ana
try:
# veritabanı tablolarının oluşturulması ve başlatılması
dao.init_database(data)
except MyException as ex:
# hata mesajı görüntüleniyor
print(f"L'erreur suivante s'est produite : {ex}")
finally:
# uygulama tarafından kullanılan kaynakların serbest bırakılması
import shutdown
shutdown.execute(config)
# son
print("Travail terminé...")
- 1-11. satırlar: Komut dosyası, MySQL veya PostgreSQL veritabanını başlatmak istendiğine bağlı olarak [mysql] veya [pgres] parametresini bekler;
- satır 13-15: Uygulama, parametre olarak geçirilen SGBD için yapılandırılmıştır;
- 20-22. satırlar: veritabanına eklenecek veriler alınır;
-
- satır: [dao] katmanı zaten oluşturulmuştur ve uygulama yapılandırmasında erişilebilir durumdadır;
- satır 30: veritabanı başlatılır;
- satır 34-37: hata olsun ya da olmasın, [shutdown] modülü kullanılarak uygulama kaynakları serbest bırakılır;
[shutdown.py] modülü şu şekildedir:
def execute(config: dict):
# uygulama tarafından kullanılan kaynakların serbest bırakılması
sqlalchemy_session = config['database']['session']
if sqlalchemy_session:
sqlalchemy_session.close()
[shutdown.execute] işlevi, veritabanını başlatmak için kullanılan [sqlalchemy] oturumunu kapatır.
[main_init_database]'i SGBD ve MySQL ile çalıştırmak için bir ilk yürütme yapılandırması oluşturuyoruz (bkz. |configuration d’exécution|):

Bu yapılandırmanın yürütülmesinin sonuçları, phpMyAdmin'te şu şekildedir:



SGBD ve [PostgreSQL] için aşağıdaki yürütme yapılandırmasını kullanıyoruz:

Çalıştırma sırasında, [pgAdmin]'teki sonuçlar şunlardır:



SGBD'i ne kadar kolay değiştirebildiğimize dikkat edin.
19.6.7. [dao] katmanı – 2
[dao] katmanını uygulayan [DatabaseDao] sınıfına geri dönüyoruz. Şu ana kadar sadece [init_database] yönteminin uygulamasını göstermiştik. Şimdi diğer yöntemlerin uygulamalarını göstereceğiz:
from sqlalchemy.exc import DatabaseError, IntegrityError, InterfaceError
from Classe import Classe
from Elève import Elève
from InterfaceDatabaseDao import InterfaceDatabaseDao
from Matière import Matière
from MyException import MyException
from Note import Note
class DatabaseDao(InterfaceDatabaseDao):
def __init__(self, config: dict):
# veritabanı = {"motor": motor, "meta veriler": meta veriler, "tablolar": tablolar, "oturum": oturum}
self.database = config['database']
self.session = self.database['session']
def init_database(self, data: dict):
…
# tüm sınıfların listesi
def get_classes(self: object) -> list:
# sorgu
return self.session.query(Classe).all()
# tüm öğrencilerin listesi
def get_élèves(self: object) -> list:
# sorgu
return self.session.query(Elève).all()
# tüm derslerin listesi
def get_matières(self: object) -> list:
# sorgu
return self.session.query(Matière).all()
# tüm öğrencilerin not listesi
def get_notes(self: object) -> list:
# istek
return self.session.query(Note).all()
# belirli bir öğrencinin not listesi
def get_notes_for_élève_by_id(self: object, élève_id: int) -> list:
# öğrenci aranıyor - öğrenci yoksa bir istisna atılır
# geri dönmesine izin verilir
élève = self.get_élève_by_id(élève_id)
# notları alınır (gecikmeli yükleme)
notes = élève.notes
# bir sözlük döndürülür
return {"élève": élève, "notes": notes}
# numarasına göre bir öğrenci tespit edilir
def get_élève_by_id(self, élève_id: int) -> Elève:
# öğrenciyi arıyoruz
élèves = self.session.query(Elève).filter(Elève.id == élève_id).all()
# bulundumu?
if élèves:
return élèves[0]
else:
raise MyException(11, f"L'élève d'identifiant {élève_id} n'existe pas")
# adından tanınan bir öğrenci
def get_élève_by_name(self, élève_name: str) -> Elève:
# öğrenci aranıyor
élèves = self.session.query(Elève).filter(Elève.nom == élève_name).all()
# bulundumu?
if élèves:
return élèves[0]
else:
raise MyException(12, f"L'élève de nom {élève_name} n'existe pas")
# numarasına göre tespit edilen bir sınıf
def get_classe_by_id(self, classe_id: int) -> Classe:
# sınıf aranıyor
classes = self.session.query(Classe).filter(Classe.id == classe_id).all()
# bulundumu?
if classes:
return classes[0]
else:
raise MyException(13, f"La classe d'identifiant {classe_id} n'existe pas")
# adıyla tanımlanan bir sınıf
def get_classe_by_name(self, classe_name: str) -> Classe:
# sınıfı arıyoruz
classes = self.session.query(Classe).filter(Classe.nom == classe_name).all()
# bulundumu?
if classes:
return classes[0]
else:
raise MyException(14, f"La classe de nom {classe_name} n'existe pas")
# numarasına göre bir ders
def get_matière_by_id(self, matière_id: int) -> Matière:
# konuyu arıyoruz
matières = self.session.query(Matière).filter(Matière.id == matière_id).all()
# bulundumu?
if matières:
return matières[0]
else:
raise MyException(11, f"La matière d'identifiant {matière_id} n'existe pas")
# adıyla tanımlanan bir madde
def get_matière_by_name(self, matière_name: str) -> Matière:
# malzeme aranıyor
matières = self.session.query(Matière).filter(Matière.nom == matière_name).all()
# bulundumu?
if matières:
return matières[0]
else:
raise MyException(15, f"La matière de nom {matière_name} n'existe pas")
- 21-24. satırlar: [get_classes] yöntemi, okulun sınıf listesini döndürmelidir. 20. satırda, daha önce karşılaştığımız bir sorgu kullanıyoruz;
- 26-39. satırlar: Öğrenci, ders ve not listelerini almak için benzer üç yöntem daha;
- satır 51-59: [get_élève_by_id] yöntemi, numarasıyla tanımlanan bir öğrenciyi döndürmelidir. Öğrenci mevcut değilse bir istisna atar;
-
- satır: Filtrelenmiş bir sorgu kullanıyoruz. Boş veya tek öğeli bir liste elde ediyoruz;
- satır 57: Alınan liste boş değilse, listenin ilk öğesi döndürülür;
- aksi takdirde 59. satırda bir istisna atılır;
-
- satır 41-49: [get_notes_for_élève_by_id] yöntemi, numarasıyla tanımlanan bir öğrencinin notlarını döndürmelidir:
-
- satırda, öğrencinin Elève varlığını elde etmek için [get_élève_by_id] yöntemi kullanılır;
-
- satırda, [Note] varlığı ile [notes] tablosu arasındaki eşlemeyle oluşturulan [Elève.notes] özelliği kullanılır (bkz. |configuration sqlalchemy| paragrafı) ve bu özellik öğrencinin notlarını temsil eder;
-
- satır: bir sözlük döndürülür;
-
- satır 61-109: aşağıdakileri yapmayı sağlayan bir dizi benzer yöntem:
- bir öğrenciyi adına göre bulmak, satır 61-69;
- bir sınıfı bulmak, satır 71-89;
- bir dersi bulmak, satır 91-109;
19.6.8. [main_joined_queries] betiği

[main_joined_queries] komut dosyası, [sqlalchemy] tarafından birden fazla tabloya ait bilgileri almak için örtük olarak yapılan sorguları ortaya çıkarmayı amaçladığı için bu adı almıştır. Programcıya görünmeyen bu sorgular, bir varlığın özelliği varlık eşlemede [relationship] işleviyle ilişkilendirildiğinde her seferinde gerçekleştirilir. Örneğin:
# eşleme
mapper(Note, tables['notes'], properties={
'id': notes_table.c.id,
'değer': notes_table.c.valeur,
'öğrenci': ilişki(Öğrenci, backref="notlar", lazy="seç"),
'ders': ilişki(Ders, backref="notlar", lazy="select")
})
Yukarıda, [Note] varlığı ile [notes] tablosu arasındaki eşleme gösterilmektedir:
-
- satırda, bir [Note] varlığının [élève] özelliği ilk kez talep edildiğinde, bu özellik bir SQL sorgusu aracılığıyla [élèves] tablosunda aranacaktır. Bu özellik talep edilene kadar tanımlanmamış olarak kalır (gecikmeli yükleme). Bir kez elde edildiğinde, değeri ORM'in belleğinde kalır. İkinci kez referans verildiğinde, ORM, yeni bir SQL sorgusu yapmadan değerini anında döndürür. Tüm bu süreç geliştirici için şeffaftır;
- aynı durum, ters özellik [Elève.notes] (backref) için de geçerlidir, 5. satır;
- aynı durum, 6. satırdaki [Note.matière] özelliği ve bunun tersi olan [Matière.notes] (backref) özelliği için de geçerlidir;
[main_joined_queries] komut dosyası şöyledir:
# mysql veya pgres parametresi bekleniyor
import sys
syntaxe = f"{sys.argv[0]} mysql / pgres"
erreur = len(sys.argv) != 2
if not erreur:
sgbd = sys.argv[1].lower()
erreur = sgbd != "mysql" and sgbd != "pgres"
if erreur:
print(f"syntaxe : {syntaxe}")
sys.exit()
# uygulama yapılandırılıyor
import config
config = config.configure({"sgbd": sgbd})
# syspath yapılandırıldı - içe aktarmalar yapılabilir
from MyException import MyException
# [dao] katmanı
dao = config["dao"]
try:
# öğrenci, kimlik numarasına göre
print("élève id=11 -----------")
élève = dao.get_élève_by_id(11)
print(f"élève={élève}")
# öğrencinin sınıfı (gecikmeli yükleme)
classe = élève.classe
print(f"classe de l'élève : {classe}")
# aynı sınıftaki öğrenciler (gecikmeli yükleme)
print("élèves dans la même classe :")
for élève in classe.élèves:
print(f"élève={élève}")
# bir öğrencinin adına göre
print("élève nom='nom2' -----------")
print(f"élève={dao.get_élève_by_name('nom2')}")
# sınıfı (gecikmeli yükleme)
print(f"classe de l'élève : {élève.classe}")
# bir öğrencinin notları
print("notes de l'élève id=11 -----------")
# önce öğrenci
élève = dao.get_élève_by_id(11)
# ardından notları (gecikmeli yükleme)
for note in élève.notes:
# not
print(f"note={note}, "
# notun konusu (gecikmeli yükleme)
f"matière={note.matière}")
# bir sınıftaki öğrenciler
print("élèves de la classe nom='classe1' -----------")
# önce sınıf
classe = dao.get_classe_by_name('classe1')
# ardından öğrenciler (gecikmeli yükleme)
for élève in classe.élèves:
print(élève)
# aynı şey [classe2] için de geçerli
print("élèves de la classe de nom 'classe2' -----------")
classe = dao.get_classe_by_name('classe2')
for élève in classe.élèves:
print(élève)
# bir dersteki notlar
print("matière de nom='matière1' -----------")
# önce ders
matière = dao.get_matière_by_name('matière1')
print(f"matière={matière}")
# ardından bu dersteki notlar (gecikmeli yükleme)
print("Notes dans la matière : ")
for note in matière.notes:
print(note)
# ders2 için de aynı şey
print("matière de nom='matière2' -----------")
matière = dao.get_matière_by_name('matière2')
print(f"matière={matière}")
print("Notes dans la matière : ")
for note in matière.notes:
print(f"note={note}")
except MyException as ex1:
# hata mesajı görüntülenir
print(f"L'erreur 1 suivante s'est produite : {ex1}")
except BaseException as ex2:
# hata mesajı görüntülenir
print(f"L'erreur 2 suivante s'est produite : {ex2}")
finally:
# kaynaklar serbest bırakılıyor
import shutdown
shutdown.execute(config)
Kodun anlaşılması için açıklamalar yeterlidir.
MySQL için bir yürütme yapılandırması oluşturulur:

Çalıştırma sonuçları şöyledir:
Bu sonuçları anlamak için, varlık sözlüğünden bazı özelliklerin hariç tutulduğunu hatırlamak gerekir (bkz. |configuration|):
# varlıkların yapılandırılması [BaseEntity]
Elève.excluded_keys = ['_sa_instance_state', 'notes', 'classe']
Classe.excluded_keys = ['_sa_instance_state', 'élèves']
Matière.excluded_keys = ['_sa_instance_state', 'notes']
Note.excluded_keys = ['_sa_instance_state', 'matière', 'élève']
Dolayısıyla, kodun 26. satırında [print(f"élève={élève}")] yazıldığında, yukarıdaki 1. satır bize ['_sa_instance_state', 'notes', 'classe'] özelliklerinin görüntülenmeyeceğini belirtir. Sonuçların 3. satırında da bunu görebiliriz. Diğer tüm özellikler görüntülenir. Dolayısıyla, yine 3. satırda, başlangıçta [Elève] varlığında bulunmayan yeni bir [classe_id] özelliği ortaya çıkar. Bu özellik, [élèves] tablosundaki [classe_id] sütununa doğrudan karşılık gelir. Böylece [sqlalchemy], [Elève] varlığına şu özellikleri eklemiştir: [classe_id, _sa_instance_state, notes]. Bunun farkında olmak gerekir; özellikle de bu özelliklerin eşlenen varlıkta halihazırda mevcut olmaması gerektiği için.
Varlık sözlüğünden hariç tutulan özellikler önemlidir. Örneğin, [notes, élève] özellikleri [Elève] varlığından hariç tutulmazsa, [print(f"élève={élève}")] işlemi bu özellikleri görüntüleyecek ve dolayısıyla, az önce açıklandığı gibi, bu özelliklerin değerlerini almak için SQL sorgularının (gecikmeli yükleme) tetiklenmesine neden olur. Burada olduğu gibi bir öğrenci listesi görüntüleniyorsa, her öğrenci için SQL işlemlerinin her biri gerçekleştirilir. Bu, bir yandan gereksiz olabilir, diğer yandan da yürütme süresi açısından kesinlikle maliyetli olabilir.
Komut dosyasını bir PostgreSQL veritabanıyla çalıştırmak için aşağıdaki yürütme yapılandırması oluşturulur:

Çalıştırma, MySQL ile aynı sonuçları verir.
19.6.9. [main_stats_for_élève] komut dosyası

[main_stats_for_élève] komut dosyası, [troiscouches v01] uygulamasında zaten kullanılan komut dosyasıdır. O zamanlar adı [main] idi. Bu, bir öğrencinin notlarına ilişkin belirli göstergeleri elde etmeyi sağlayan bir konsol uygulamasıdır: [moyenne pondérée, min, max, liste]. Aşağıdaki mimariye yerleştirilir:

Bu katmanlı mimaride, |troiscouches v01| uygulaması ile bu uygulama arasında yalnızca [dao] katmanı değiştirilmiştir. Yeni [dao] katmanı, eski [dao] katmanının [InterfaceDao] arayüzüne uyumlu olduğundan, [ui, métier] katmanlarının değiştirilmesine gerek yoktur. Dolayısıyla, |troiscouches v01| uygulamasında tanımlanan katmanları kullanmaya devam edilebilir.
[main_stats_for_élève] komut dosyası, yukarıdaki şemadaki [main] katmanını şu şekilde uygular:
# mysql veya pgres parametresi bekleniyor
import sys
syntaxe = f"{sys.argv[0]} mysql / pgres"
erreur = len(sys.argv) != 2
if not erreur:
sgbd = sys.argv[1].lower()
erreur = sgbd != "mysql" and sgbd != "pgres"
if erreur:
print(f"syntaxe : {syntaxe}")
sys.exit()
# uygulama yapılandırılıyor
import config
config = config.configure({'sgbd': sgbd})
# syspath yapılandırıldı - içe aktarmalar yapılabilir
from MyException import MyException
# [ui] katmanı
ui = config["ui"]
try:
# [ui] katmanı çalıştırılıyor
ui.run()
except MyException as ex1:
# hata görüntüleniyor
print(f"L'erreur 1 suivante s'est produite : {ex1}")
except BaseException as ex2:
# hata görüntülenir
print(f"L'erreur 2 suivante s'est produite : {ex2}")
finally:
# kaynaklar serbest bırakılıyor
import shutdown
shutdown.execute(config)
-
- satır: uygulama yapılandırmasında [ui] katmanına ilişkin bir referans alınır;
-
- satır: [ui] katmanının tek yöntemi kullanılarak kullanıcıyla diyalog başlatılır;
PostgreSQL için bir yürütme yapılandırması şu şekilde olur:

İşte bu yapılandırma ile bir yürütme örneği:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/databases/sqlalchemy/05/main/main_stats_for_élève.py pgres
Numéro de l'élève (>=1 et * pour arrêter) : 11
Elève={"prénom": "prénom1", "id": 11, "classe_id": 1, "nom": "nom1"}, notes=[10.0 6.0], max=10.0, min=6.0, moyenne pondérée=7.33
Numéro de l'élève (>=1 et * pour arrêter) : 1
L'erreur suivante s'est produite : MyException[11, L'élève d'identifiant 1 n'existe pas]
Numéro de l'élève (>=1 et * pour arrêter) : *
Process finished with exit code 0