Skip to content

18. SGBD’ten bağımsız kod yazma

Daha önce, bazı durumlarda SGBD ve MySQL için yazılmış Python kodunu, SGBD ve PostgreSQL için yazılmış koda kolayca taşıyabileceğimizi görmüştük. Bu bölümde, bu yaklaşımı nasıl sistematik hale getirebileceğimizi göstereceğiz. Önerilen mimari şu şekilde olacaktır:

Image

Konektörün ve dolayısıyla SGBD'in seçiminin yapılandırma yoluyla yapılmasını ve komut dosyasının yeniden yazılmasını gerektirmemesini istiyoruz. Bunun, komut dosyasının SGBD'e özgü uzantıları kullanmadığı durumlarda mümkün olduğunu hatırlatırız.

Komut dosyalarının dizin yapısı şu şekilde olacaktır:

Image

[any_xx] komut dosyaları, daha önce SGBD, MySQL ve PostgreSQL için incelenen komut dosyalarını temel almaktadır. Hepsini tekrarlamayacağız. En karmaşık olan [any_04] komut dosyasına odaklanacağız. Bu komut dosyasının, aşağıdaki [data/commandes.sql] dosyasındaki SQL komutlarını çalıştırdığını hatırlatırız:


# [personnes] tablosu siliniyor
drop table if exists personnes
# kişiler tablosunun oluşturulması
create table personnes (id int primary key, prenom varchar(30) not null, nom varchar(30) not null, age integer not null, unique (nom,prenom))
# iki kişinin eklenmesi
insert into personnes(id, prenom, nom, age) values(1, 'Paul','Langevin',48)
insert into personnes(id, prenom, nom, age) values (2, 'Sylvie','Lefur',70)
# tablonun görüntülenmesi
select prenom, nom, age from personnes
# kasıtlı hata
xx
# üç kişinin eklenmesi
insert into personnes(id, prenom, nom, age) values (3, 'Pierre','Nicazou',35)
insert into personnes(id, prenom, nom, age) values (4, 'Geraldine','Colou',26)
insert into personnes(id, prenom, nom, age) values (5, 'Paulette','Girond',56)
# tablonun görüntülenmesi
select prenom, nom, age from personnes
# kişilerin soyadlarına göre alfabetik sırayla ve aynı soyadlıların ise adlarına göre alfabetik sırayla listelenmesi
select nom,prenom from personnes order by nom asc, prenom desc
# [20,40] aralığında 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 listelenir
select nom,prenom,age from personnes where age between 20 and 40 order by age desc, nom asc, prenom asc
# Bayan Bruneau'nun listeye eklenmesi
insert into personnes(id, prenom, nom, age) values(6, 'Josette','Bruneau',46)
# yaşının güncellenmesi
update personnes set age=47 where nom='Bruneau'
# soyadı Bruneau olan kişilerin listesi
select nom,prenom,age from personnes where nom='Bruneau'
# Bayan Bruneau'nun silinmesi
delete from personnes where nom='Bruneau'
# soyadı Bruneau olan kişilerin listesi
select nom,prenom,age from personnes where nom='Bruneau'

[personnes] tablosu mevcut değilse, komutun SGBD, MySQL ve PostgreSQL için de aynı şekilde davranması amacıyla 2. satırı değiştirdik.

[any_04] komut dosyası, aşağıdaki [config.py] komut dosyası tarafından yapılandırılmıştır:


def configure():
    import os

    # bu komut dosyasının klasörünün tam yolu
    script_dir = os.path.dirname(os.path.abspath(__file__))

    # syspath klasörlerinin yapılandırılması
    absolute_dependencies = [
        # yerel klasörler
        f"{script_dir}/shared",
    ]

    # syspath'ın ayarlanması
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # uygulama yapılandırması
    config = {
        # yönetilen veritabanı yönetim sistemleri
        "sgbds": {
            "mysql": {
                # DBMS bağlayıcısı
                "sgbd_connector""mysql.connector",
                # DBMS yönetim işlevlerini bir araya getiren modülün adı
                "sgbd_fonctions""any_module",
                # bağlantı kimlik bilgileri
                "user""admpersonnes",
                "password""nobody",
                "host""localhost",
                "database""dbpersonnes"
            },
            "postgresql": {
                # DBMS konektörü
                "sgbd_connector""psycopg2",
                # DBMS yönetim işlevlerini bir araya getiren modülün adı
                "sgbd_fonctions""any_module",
                # bağlantı kimlikleri
                "user""admpersonnes",
                "password""nobody",
                "host""localhost",
                "database""dbpersonnes"
            }
        },
        # komut dosyası SQL
        "commands_filename"f"{script_dir}/data/commandes.sql"
    }
    # uygulamanın syspath'i
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # yapılandırma
    return config

Yenilik, 18-43. satırlarda yer almaktadır:

    1. satır: [sgbds], 21. satırdaki [mysql] ve 32. satırdaki [postgresql] olmak üzere iki anahtara sahip bir sözlüktür;
  • bu anahtarlara atanan değer, bir SGBD'e bağlanmayı sağlayan öğeleri içeren bir sözlüktür:
  • 21-32. satırlar: SGBD ve MySQL'e bağlantı için gerekli öğeler;
    • satır 23: kullanılacak Python bağlayıcısı;
      1. satır: ortak işlevleri içeren modül;
    • 26-30. satırlar: bağlantının kimlik bilgileri;
  • 32-41. satırlar: SGBD PostgreSQL bağlantısı için aynı unsurlar;

SQL ve [data/commandes.sql] komut dosyalarını çalıştıran [any_04] betiği şöyledir:


# uygulamayı yapılandırıyoruz
import config

config = config.configure()

# syspath yapılandırıldı - içe aktarmalar yapılabilir
import importlib
import sys

# çağrı sözdiziminin doğrulanması
# argv[0] sgbd_name true / false
# 3 parametre gerekiyor
args = sys.argv
erreur = len(args) != 3
if not erreur:
    # ilgilendiğimiz iki parametre alınır
    sgbd_name = args[1].lower()
    with_transaction = args[2].lower()
    # iki parametrenin doğrulanması
    erreur = (with_transaction != "true" and with_transaction != "false") \
             or sgbd_name not in config["sgbds"].keys()
# hata mı?
if erreur:
    print(f"syntaxe : {args[0]} (1) sgbd_name (2) true / false")
    sys.exit()

# sgbd_name'in yapılandırılması
sgbd_config = config["sgbds"][sgbd_name]
# sgbd_name konektörü
sgbd_connector = importlib.import_module(sgbd_config["sgbd_connector"])
# fonksiyon kütüphanesi
lib = importlib.import_module(sgbd_config["sgbd_fonctions"])


# görüntülenecek metnin hesaplanması
with_transaction = with_transaction == "true"
if with_transaction:
    texte = "avec transaction"
else:
    texte = "sans transaction"

# görüntüleme
commands_filename=config['commands_filename']
print("--------------------------------------------------------------------")
print(f"Exécution du fichier SQL {commands_filename} {texte}")
print("--------------------------------------------------------------------")

# dosyadaki SQL komutlarının yürütülmesi
connexion = None
try:
    # bağlantı
    connexion = sgbd_connector.connect(
        host=sgbd_config['host'],
        user=sgbd_config['user'],
        password=sgbd_config['password'],
        database=sgbd_config['database'])
    # SQL komut dosyasının yürütülmesi
    erreurs = lib.execute_file_of_commands(sgbd_connector, connexion, commands_filename, suivi=True, arrêt=False,
                                           with_transaction=with_transaction)
except (sgbd_connector.InterfaceError, sgbd_connector.DatabaseError) as erreur:
    print(f"L'erreur suivante s'est produite : {erreur}")
finally:
    # bağlantının kapatılması
    if connexion:
        connexion.close()

# hata sayısının görüntülenmesi
print("--------------------------------------------------------------------")
print(f"Exécution terminée")
print("--------------------------------------------------------------------")
print(f"Il y a eu {len(erreurs)} erreur(s)")
# hataların görüntülenmesi
for erreur in erreurs:
    print(erreur)

Açıklamalar

  • 1-4. satırlar: Uygulamanın [config] yapılandırması alınır;
  • 10-21. satırlar: Komut dosyası, iki parametreyle çağrılır: [sgbd_name with_transaction]:
    • [sgbd_name]: Kullanılacak SGBD'in adı;
    • [with_transaction]: SQL komut dosyasını bir işlem içinde çalıştırmak isteniyorsa True, aksi takdirde False;
  • 10-25. satırlar: parametreler alınır ve doğrulanır;
    1. satır: Seçilen SGBD'in yapılandırması;
  • satır 30: Seçilen SGBD'in konektörü içe aktarılır. Bunun için, adı bir değişkende bulunan bir modülü içe aktarmaya olanak tanıyan [importlib] kütüphanesi (satır 7) kullanılır. [importlib.import_module] işleminin sonucu bir modüldür. Dolayısıyla 30. satırdan sonra, sanki şu komut yürütülmüş gibi devam edilir:
import sgbd_connector

Bu sayede, [sgbd_connector] modülünün [connect] işlevini kullandığımız 52. satırda [sgbd_connector.connect] yazabileceğiz. Burada şunu unutmamak gerekir ki, [sgbd_connector], ya [mysql.connector] ya da [psycopg2]'tir. Bu iki modül de [connect] işlevine sahiptir. Aynı şekilde, 60. satıra [sgbd_connector.InterfaceError, sgbd_connector.DatabaseError] yazılabilir.

    1. satır: komut dosyası tarafından kullanılan işlev modülü içe aktarılır;
    1. satır: komut dosyası tarafından kullanılan işlev modülündeki [execute_file_of_commands] işlevi çalıştırılır. Önceki sürümlerle karşılaştırıldığında, bu işlevin imzası bir parametre daha fazladır; bu, ilk parametredir. İşleve, kullanması gereken Python konektörü [sgbd_connector] aktarılır;
  • bu noktalar dışında, [any_04] komut dosyası önceki sürümlerdeki haliyle kalır;

[any_module] işlev kütüphanesi şu şekildedir:


# ---------------------------------------------------------------------------------

def afficher_infos(curseur):
    


# ---------------------------------------------------------------------------------
def execute_list_of_commands(sgbd_connector, connexion, sql_commands: list,
                             suivi: bool = False, arrêt: bool = True, with_transaction: bool = True):
    

    # başlatma işlemleri
    curseur = None
    connexion.autocommit = not with_transaction
    erreurs = []
    try:
        # imleç isteniyor
        curseur = connexion.cursor()
        # sql_commands içinde bulunan sql_commands komutlarının yürütülmesi
        # bunlar tek tek çalıştırılıyor
        for command in sql_commands:
            # geçerli komutun başındaki ve sonundaki boşluklar kaldırılır
            command = command.strip()
            # boş bir komut mu yoksa bir yorum mu var? Varsa, bir sonraki komuta geçilir
            if command == '' or command[0] == "#":
                continue
            # geçerli komutun yürütülmesi
            error = None
            try:
                curseur.execute(command)
            except (sgbd_connector.InterfaceError, sgbd_connector.DatabaseError) as erreur:
                error = erreur
            # Bir hata oluştu mu?
            


# ---------------------------------------------------------------------------------
def execute_file_of_commands(sgbd_connector, connexion, sql_filename: str,
                             suivi: bool = False, arrêt: bool = True, with_transaction: bool = True):
    

    # SQL dosyasının işlenmesi
    try:
        # dosyanın okuma modunda açılması
        file = open(sql_filename, "r")
        # işleme
        return execute_list_of_commands(sgbd_connector, connexion, file.readlines(), suivi, arrêt, with_transaction)
    except BaseException as erreur:
        # hata tablosu döndürülüyor
        return [f"Le fichier {sql_filename} n'a pu être être exploité : {erreur}"]
    finally:
        pass

[sgbd_connector] parametresi, 31. satırda yakalanan istisnaların türünü belirtmek için kullanılmıştır.

[mysql false] parametreleriyle [any_04] komut dosyasının çalıştırılması aşağıdaki sonuçları vermektedir:


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/anysgbd/any_04.py mysql false
--------------------------------------------------------------------
Exécution du fichier SQL C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\databases\anysgbd/data/commandes.sql sans transaction
--------------------------------------------------------------------
[drop table if exists personnes] : Exécution réussie
nombre de lignes modifiées : 0
[create table personnes (id int primary key, prenom varchar(30) not null, nom varchar(30) not null, age integer not null, unique (nom,prenom))] : Exécution réussie
nombre de lignes modifiées : 0
[insert into personnes(id, prenom, nom, age) values(1, 'Paul','Langevin',48)] : Exécution réussie
nombre de lignes modifiées : 1
[insert into personnes(id, prenom, nom, age) values (2, 'Sylvie','Lefur',70)] : Exécution réussie
nombre de lignes modifiées : 1
[select prenom, nom, age from personnes] : Exécution réussie
prenom, nom, age,
*****************
('Paul', 'Langevin', 48)
('Sylvie', 'Lefur', 70)
*****************
xx : Erreur (1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'xx' at line 1)
[insert into personnes(id, prenom, nom, age) values (3, 'Pierre','Nicazou',35)] : Exécution réussie
nombre de lignes modifiées : 1
[insert into personnes(id, prenom, nom, age) values (4, 'Geraldine','Colou',26)] : Exécution réussie
nombre de lignes modifiées : 1
[insert into personnes(id, prenom, nom, age) values (5, 'Paulette','Girond',56)] : Exécution réussie
nombre de lignes modifiées : 1
[select prenom, nom, age from personnes] : Exécution réussie
prenom, nom, age,
*****************
('Paul', 'Langevin', 48)
('Sylvie', 'Lefur', 70)
('Pierre', 'Nicazou', 35)
('Geraldine', 'Colou', 26)
('Paulette', 'Girond', 56)
*****************
[select nom,prenom from personnes order by nom asc, prenom desc] : Exécution réussie
nom, prenom,
************
('Colou', 'Geraldine')
('Girond', 'Paulette')
('Langevin', 'Paul')
('Lefur', 'Sylvie')
('Nicazou', 'Pierre')
************
[select nom,prenom,age from personnes where age between 20 and 40 order by age desc, nom asc, prenom asc] : Exécution réussie
nom, prenom, age,
*****************
('Nicazou', 'Pierre', 35)
('Colou', 'Geraldine', 26)
*****************
[insert into personnes(id, prenom, nom, age) values(6, 'Josette','Bruneau',46)] : Exécution réussie
nombre de lignes modifiées : 1
[update personnes set age=47 where nom='Bruneau'] : Exécution réussie
nombre de lignes modifiées : 1
[select nom,prenom,age from personnes where nom='Bruneau'] : Exécution réussie
nom, prenom, age,
*****************
('Bruneau', 'Josette', 47)
*****************
[delete from personnes where nom='Bruneau'] : Exécution réussie
nombre de lignes modifiées : 1
[select nom,prenom,age from personnes where nom='Bruneau'] : Exécution réussie
nom, prenom, age,
*****************
*****************
--------------------------------------------------------------------
Exécution terminée
--------------------------------------------------------------------
Il y a eu 1 erreur(s)
xx : Erreur (1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'xx' at line 1)

Process finished with exit code 0