Skip to content

18. SGBD-unabhängigen Code schreiben

Wir haben zuvor gesehen, dass es in bestimmten Fällen möglich ist, Python-Code, der für SGBD und MySQL geschrieben wurde, einfach auf Code zu portieren, der für SGBD und PostgreSQL geschrieben wurde. In diesem Kapitel zeigen wir, wie sich dieser Ansatz systematisieren lässt. Die vorgeschlagene Architektur sieht wie folgt aus:

Image

Wir möchten, dass die Auswahl des Konnektors und damit des SGBD über die Konfiguration erfolgt und keine Neuprogrammierung des Skripts erfordert. Wir weisen darauf hin, dass dies nur in Fällen möglich ist, in denen das Skript keine proprietären Erweiterungen des SGBD verwendet.

Die Skriptstruktur sieht wie folgt aus:

Image

Die Skripte [any_xx] basieren auf den bereits untersuchten Skripten für die SGBD, MySQL und PostgreSQL. Wir werden nicht alle erneut behandeln. Wir konzentrieren uns auf das Skript [any_04], das am komplexesten ist. Zur Erinnerung: Dieses Skript führt die Befehle SQL aus der folgenden Datei [data/commandes.sql] aus:


# Löschen der Tabelle [personnes]
drop table if exists personnes
# Anlegen der Tabelle „Personen“
create table personnes (id int primary key, prenom varchar(30) not null, nom varchar(30) not null, age integer not null, unique (nom,prenom))
# Einfügen von zwei Personen
insert into personnes(id, prenom, nom, age) values(1, 'Paul','Langevin',48)
insert into personnes(id, prenom, nom, age) values (2, 'Sylvie','Lefur',70)
# Anzeige der Tabelle
select prenom, nom, age from personnes
# absichtlicher Fehler
xx
# Einfügen von drei Personen
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)
# Anzeige der Tabelle
select prenom, nom, age from personnes
# Liste der Personen in alphabetischer Reihenfolge der Nachnamen und bei gleichen Nachnamen in alphabetischer Reihenfolge der Vornamen
select nom,prenom from personnes order by nom asc, prenom desc
# Liste der Personen, deren Alter im Intervall [20,40] liegt, in absteigender Reihenfolge nach dem Alter
# anschließend bei gleichem Alter in alphabetischer Reihenfolge der Nachnamen und bei gleichen Nachnamen in alphabetischer Reihenfolge der Vornamen
select nom,prenom,age from personnes where age between 20 and 40 order by age desc, nom asc, prenom asc
# Einfügung von Frau Bruneau
insert into personnes(id, prenom, nom, age) values(6, 'Josette','Bruneau',46)
# Aktualisierung ihres Alters
update personnes set age=47 where nom='Bruneau'
# Liste der Personen mit dem Nachnamen Bruneau
select nom,prenom,age from personnes where nom='Bruneau'
# Löschung von Frau Bruneau
delete from personnes where nom='Bruneau'
# Liste der Personen mit dem Nachnamen Bruneau
select nom,prenom,age from personnes where nom='Bruneau'

Wir haben Zeile 2 so geändert, dass der Befehl bei den Skripten SGBD, MySQL und PostgreSQL dasselbe Verhalten zeigt, wenn die Tabelle [personnes] nicht existiert.

Das Skript [any_04] wird durch das folgende Skript [config.py] konfiguriert:


def configure():
    import os

    # Absoluter Pfad zum Ordner dieses Skripts
    script_dir = os.path.dirname(os.path.abspath(__file__))

    # Konfiguration der Verzeichnisse im Syspath
    absolute_dependencies = [
        # lokale Ordner
        f"{script_dir}/shared",
    ]

    # Festlegung des Syspath
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # Konfiguration der Anwendung
    config = {
        # Unterstützte DBMS
        "sgbds": {
            "mysql": {
                # DBMS-Konnektor
                "sgbd_connector""mysql.connector",
                # Name des Moduls, das die Verwaltungsfunktionen für das DBMS bündelt
                "sgbd_fonctions""any_module",
                # Anmeldedaten
                "user""admpersonnes",
                "password""nobody",
                "host""localhost",
                "database""dbpersonnes"
            },
            "postgresql": {
                # DBMS-Konnektor
                "sgbd_connector""psycopg2",
                # Name des Moduls, das die Verwaltungsfunktionen des DBMS bündelt
                "sgbd_fonctions""any_module",
                # Anmeldedaten
                "user""admpersonnes",
                "password""nobody",
                "host""localhost",
                "database""dbpersonnes"
            }
        },
        # Befehlsdatei SQL
        "commands_filename"f"{script_dir}/data/commandes.sql"
    }
    # Syspath der Anwendung
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # Konfiguration wird übermittelt
    return config

Die Neuerung befindet sich in den Zeilen 18–43:

  • Zeile 20: [sgbds] ist ein Wörterbuch mit zwei Schlüsseln: [mysql] in Zeile 21 und [postgresql] in Zeile 32;
  • der diesen Schlüsseln zugeordnete Wert ist ein Wörterbuch, das die Elemente enthält, die die Verbindung zu einem SGBD ermöglichen:
  • Zeilen 21–32: Die Elemente einer Verbindung zum SGBD MySQL;
    • Zeile 23: der zu verwendende Python-Connector;
    • Zeile 25: das Modul, das gemeinsam genutzte Funktionen enthält;
    • Zeilen 26–30: die Anmeldedaten für die Verbindung;
  • Zeilen 32–41: dieselben Elemente für eine Verbindung zu SGBD und PostgreSQL;

Das Skript [any_04], das die Befehlsdatei SQL [data/commandes.sql] ausführt, lautet wie folgt:


# die Anwendung wird konfiguriert
import config

config = config.configure()

# Der Syspath ist konfiguriert – die Importe können nun durchgeführt werden
import importlib
import sys

# Überprüfung der Syntax des Aufrufs
# argv[0] sgbd_name true / false
# Es sind 3 Parameter erforderlich
args = sys.argv
erreur = len(args) != 3
if not erreur:
    # Wir erhalten die beiden Parameter, die uns interessieren
    sgbd_name = args[1].lower()
    with_transaction = args[2].lower()
    # Überprüfung der beiden Parameter
    erreur = (with_transaction != "true" and with_transaction != "false") \
             or sgbd_name not in config["sgbds"].keys()
# Fehler?
if erreur:
    print(f"syntaxe : {args[0]} (1) sgbd_name (2) true / false")
    sys.exit()

# Konfiguration des sgbd_name
sgbd_config = config["sgbds"][sgbd_name]
# Anschluss des sgbd_name
sgbd_connector = importlib.import_module(sgbd_config["sgbd_connector"])
# Funktionsbibliothek
lib = importlib.import_module(sgbd_config["sgbd_fonctions"])


# Berechnung eines anzuzeigenden Textes
with_transaction = with_transaction == "true"
if with_transaction:
    texte = "avec transaction"
else:
    texte = "sans transaction"

# Anzeige
commands_filename=config['commands_filename']
print("--------------------------------------------------------------------")
print(f"Exécution du fichier SQL {commands_filename} {texte}")
print("--------------------------------------------------------------------")

# Ausführung der Befehle SQL aus der Datei
connexion = None
try:
    # Anmeldung
    connexion = sgbd_connector.connect(
        host=sgbd_config['host'],
        user=sgbd_config['user'],
        password=sgbd_config['password'],
        database=sgbd_config['database'])
    # Ausführung der Befehlsdatei SQL
    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:
    # Schließen der Verbindung
    if connexion:
        connexion.close()

# Anzeige der Fehleranzahl
print("--------------------------------------------------------------------")
print(f"Exécution terminée")
print("--------------------------------------------------------------------")
print(f"Il y a eu {len(erreurs)} erreur(s)")
# Anzeige der Fehler
for erreur in erreurs:
    print(erreur)

Kommentare

  • Zeilen 1–4: Die Konfiguration [config] der Anwendung wird abgerufen;
  • Zeilen 10–21: Das Skript wird mit zwei Parametern aufgerufen: [sgbd_name with_transaction]:
    • [sgbd_name]: Name des zu verwendenden SGBD;
    • [with_transaction]: „True“, wenn die Skriptdatei SQL innerhalb einer Transaktion ausgeführt werden soll, andernfalls „False“;
  • Zeilen 10–25: Die Parameter werden abgerufen und überprüft;
  • Zeile 28: Konfiguration des ausgewählten SGBD;
  • Zeile 30: Der Konnektor des ausgewählten SGBD wird importiert. Dazu wird die Bibliothek [importlib] (Zeile 7) verwendet, die es ermöglicht, ein Modul zu importieren, dessen Name in einer Variablen gespeichert ist. Das Ergebnis der Operation [importlib.import_module] ist ein Modul. Nach Zeile 30 verhält sich das Programm also so, als wäre folgende Anweisung ausgeführt worden:
import sgbd_connector

Dadurch können wir in Zeile 52 „[sgbd_connector.connect]“ schreiben, wobei wir die Funktion „[connect]“ aus dem Modul „[sgbd_connector]“ verwenden. Dabei ist zu beachten, dass [sgbd_connector] entweder [mysql.connector] oder [psycopg2] ist. Diese beiden Module verfügen über die Funktion [connect]. Ebenso kann in Zeile 60 „[sgbd_connector.InterfaceError, sgbd_connector.DatabaseError]“ geschrieben werden.

  • Zeile 32: Das Modul mit den vom Skript verwendeten Funktionen wird importiert;
  • Zeile 58: Die Funktion [execute_file_of_commands] aus dem Funktionsmodul, das vom Skript verwendet wird, wird ausgeführt. Im Vergleich zu früheren Versionen hat die Signatur dieser Funktion einen Parameter mehr, nämlich den ersten. Der Python-Konnektor [sgbd_connector], den die Funktion verwenden soll, wird an sie übergeben;
  • Abgesehen von diesen Punkten bleibt das Skript [any_04] unverändert gegenüber den vorherigen Versionen;

Die Funktionsbibliothek [any_module] sieht wie folgt aus:


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

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):
    

    # Initialisierungen
    curseur = None
    connexion.autocommit = not with_transaction
    erreurs = []
    try:
        # Cursor wird angefordert
        curseur = connexion.cursor()
        # Ausführung der in sql_commands enthaltenen SQL
        # sie werden nacheinander ausgeführt
        for command in sql_commands:
            # Die Leerzeichen am Anfang und am Ende des aktuellen Befehls werden entfernt
            command = command.strip()
            # Handelt es sich um einen leeren Befehl oder einen Kommentar? Wenn ja, geht man zum nächsten Befehl über
            if command == '' or command[0] == "#":
                continue
            # Ausführung des aktuellen Befehls
            error = None
            try:
                curseur.execute(command)
            except (sgbd_connector.InterfaceError, sgbd_connector.DatabaseError) as erreur:
                error = erreur
            # Ist ein Fehler aufgetreten?
            


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

    # Auswertung der Datei SQL
    try:
        # Datei zum Lesen öffnen
        file = open(sql_filename, "r")
        # Auswertung
        return execute_list_of_commands(sgbd_connector, connexion, file.readlines(), suivi, arrêt, with_transaction)
    except BaseException as erreur:
        # Es wird ein Fehlerarray zurückgegeben
        return [f"Le fichier {sql_filename} n'a pu être être exploité : {erreur}"]
    finally:
        pass

Der Parameter [sgbd_connector] wurde in Zeile 31 verwendet, um den Typ der abgefangenen Ausnahmen anzugeben.

Die Ausführung des Skripts [any_04] mit den Parametern [mysql false] liefert folgende Ergebnisse:


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