IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Introduction à Python 3 et au framework web Flask par l'exemple


précédentsommairesuivant

18. Ecrire du code indépendant du SGBD

Nous avons vu précédemment que dans certains cas il était possible de migrer simplement du code Python écrit pour le SGBD MySQL vers du code écrit pour le SGBD PostgreSQL. Dans ce chapitre, nous montrons comment systématiser cette approche. L’architecture proposée devient la suivante :

Image non disponible

On souhaite que le choix du connecteur et donc du SGBD se fasse par configuration et ne nécessite pas de réécriture du script. On rappelle que cela n’est possible que dans les cas où le script n’utilise pas d’extensions propriétaires du SGBD.

L’arborescence des scripts sera la suivante :

Image non disponible

Les scripts [any_xx] reprennent les scripts déjà étudiés pour les SGBD MySQL et PostgreSQL. Nous n’allons pas tous les reprendre. Nous allons nous concentrer sur le script [any_04] qui est le plus complexe. On rappelle que ce script exécute les commandes SQL du fichier [data/commandes.sql] suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
# suppression de la table [personnes]
drop table if exists personnes
# création de la table personnes
create table personnes (id int primary key, prenom varchar(30) not null, nom varchar(30) not null, age integer not null, unique (nom,prenom))
# insertion de deux personnes
insert into personnes(id, prenom, nom, age) values(1, 'Paul','Langevin',48)
insert into personnes(id, prenom, nom, age) values (2, 'Sylvie','Lefur',70)
# affichage de la table
select prenom, nom, age from personnes
# erreur volontaire
xx
# insertion de trois personnes
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)
# affichage de la table
select prenom, nom, age from personnes
# liste des personnes par ordre alphabétique des noms et à nom égal par ordre alphabétique des prénoms
select nom,prenom from personnes order by nom asc, prenom desc
# liste des personnes ayant un âge dans l'intervalle [20,40] par ordre décroissant de l'âge
# puis à âge égal par ordre alphabétique des noms et à nom égal par ordre alphabétique des prénoms
select nom,prenom,age from personnes where age between 20 and 40 order by age desc, nom asc, prenom asc
# insertion de mme Bruneau
insert into personnes(id, prenom, nom, age) values(6, 'Josette','Bruneau',46)
# mise à jour de son âge
update personnes set age=47 where nom='Bruneau'
# liste des personnes ayant Bruneau pour nom
select nom,prenom,age from personnes where nom='Bruneau'
# suppression de Mme Bruneau
delete from personnes where nom='Bruneau'
# liste des personnes ayant Bruneau pour nom
select nom,prenom,age from personnes where nom='Bruneau'

Nous avons modifié la ligne 2 pour que la commande ait le même comportement pour les SGBD MySQL et PostgreSQL si la table [personnes] n’existe pas.

Le script [any_04] est configuré par le script [config.py] suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
def configure():
    import os

    # chemin absolu du dossier de ce script
    script_dir = os.path.dirname(os.path.abspath(__file__))

    # configuration des dossiers du syspath
    absolute_dependencies = [
        # dossiers locaux
        f"{script_dir}/shared",
    ]

    # fixation du syspath
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # configuration de l'application
    config = {
        # sgbd gérés
        "sgbds": {
            "mysql": {
                # connecteur du sgbd
                "sgbd_connector""mysql.connector",
                # nom du module rassemblant des fonctions de gestion du sgbd
                "sgbd_fonctions""any_module",
                # identifiants de la connexion
                "user""admpersonnes",
                "password""nobody",
                "host""localhost",
                "database""dbpersonnes"
            },
            "postgresql": {
                # connecteur du sgbd
                "sgbd_connector""psycopg2",
                # nom du module rassemblant des fonctions de gestion du sgbd
                "sgbd_fonctions""any_module",
                # identifiants de la connexion
                "user""admpersonnes",
                "password""nobody",
                "host""localhost",
                "database""dbpersonnes"
            }
        },
        # fichier de commandes SQL
        "commands_filename"f"{script_dir}/data/commandes.sql"
    }
    # syspath de l'application
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # on rend la config
    return config

La nouveauté réside dans les lignes 18-43 :

  • ligne 20 : [sgbds] est un dictionnaire avec deux clés [mysql] ligne 21 et [postgresql] ligne 32 ;

  • la valeur associée à ces clés est un dictionnaire donnant les éléments permettant la connexion à un SGBD :

  • lignes 21-32 : les éléments d’une connexion au SGBD MySQL ;

    • ligne 23 : le connecteur Python à utiliser ;

    • ligne 25 : le module contenant des fonctions partagées ;

    • lignes 26-30 : les identifiants de la connexion ;

  • lignes 32-41 : les mêmes éléments pour une connexion au SGBD PostgreSQL ;

Le script [any_04] qui exécute le fichier de commandes SQL [data/commandes.sql] est le suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
# on configure l'application
import config

config = config.configure()

# le syspath est configuré - on peut faire les imports
import importlib
import sys

# vérification de la syntaxe de l'appel
# argv[0] sgbd_name true / false
# il faut 3 paramètres
args = sys.argv
erreur = len(args) != 3
if not erreur:
    # on récupère les deux paramètres qui nous intéressent
    sgbd_name = args[1].lower()
    with_transaction = args[2].lower()
    # vérification des deux paramètres
    erreur = (with_transaction != "true" and with_transaction != "false") \
             or sgbd_name not in config["sgbds"].keys()
# erreur ?
if erreur:
    print(f"syntaxe : {args[0]} (1) sgbd_name (2) true / false")
    sys.exit()

# configuration du sgbd_name
sgbd_config = config["sgbds"][sgbd_name]
# connecteur du sgbd_name
sgbd_connector = importlib.import_module(sgbd_config["sgbd_connector"])
# bibliothèque de fonctions
lib = importlib.import_module(sgbd_config["sgbd_fonctions"])


# calcul d'un texte à afficher
with_transaction = with_transaction == "true"
if with_transaction:
    texte = "avec transaction"
else:
    texte = "sans transaction"

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

# exécution des ordres SQL du fichier
connexion = None
try:
    # connexion
    connexion = sgbd_connector.connect(
        host=sgbd_config['host'],
        user=sgbd_config['user'],
        password=sgbd_config['password'],
        database=sgbd_config['database'])
    # exécution du fichier des commandes 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:
    # fermeture de la connexion
    if connexion:
        connexion.close()

# affichage nombre d'erreurs
print("--------------------------------------------------------------------")
print(f"Exécution terminée")
print("--------------------------------------------------------------------")
print(f"Il y a eu {len(erreurs)} erreur(s)")
# affichage des erreurs
for erreur in erreurs:
    print(erreur)

Commentaires

  • lignes 1-4 : on récupère la configuration [config] de l’application ;

  • lignes 10-21 : le script s’appelle avec deux paramètres [sgbd_name with_transaction] :

    • [sgbd_name] : le nom du SGBD à utiliser ;

    • [with_transaction] : True si on veut exécuter le fichier de commandes SQL au sein d’une transaction, False sinon ;

  • lignes 10-25 : les paramètres sont récupérés et vérifiés ;

  • ligne 28 : la configuration du SGBD choisi ;

  • ligne 30 : on importe le connecteur du SGBD choisi. On utilise pour cela la bibliothèque [importlib] (ligne 7) qui permet d’importer un module dont le nom est dans une variable. Le résultat de l’opération [importlib.import_module] est un module. Ainsi après la ligne 30, tout se passe comme si l’instruction exécutée avait été :

     
    Sélectionnez
    1.
    import sgbd_connector
    

    Ceci va nous permettre d’écrire ligne 52 [sgbd_connector.connect] où on utilise la fonction [connect] du module [sgbd_connector]. Il faut se rappeler ici que [sgbd_connector] est soit [mysql.connector] ou [psycopg2]. Ces deux modules ont la fonction [connect]. De même, ligne 60, on peut écrire [sgbd_connector.InterfaceError, sgbd_connector.DatabaseError].

  • ligne 32 : on importe le module des fonctions utilisées par le script ;

  • ligne 58 : on exécute la fonction [execute_file_of_commands] du module des fonctions utilisées par le script. Par rapport aux versions précédentes, la signature de cette fonction a un paramètre de plus, le premier. On passe à la fonction le connecteur Python [sgbd_connector] qu’elle doit utiliser ;

  • en-dehors de ces points, le script [any_04] reste ce qu’il était dans les versions précédentes ;

La bibliothèque de fonctions [any_module] est la suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
# ---------------------------------------------------------------------------------

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

    # initialisations
    curseur = None
    connexion.autocommit = not with_transaction
    erreurs = []
    try:
        # on demande un curseur
        curseur = connexion.cursor()
        # exécution des sql_commands SQL contenues dans sql_commands
        # on les exécute une à une
        for command in sql_commands:
            # on élimine les blancs de début et de fin de la commande courante
            command = command.strip()
            # a-t-on une commande vide ou un commentaire ? Si oui, on passe à la commande suivante
            if command == '' or command[0] == "#":
                continue
            # exécution de la commande courante
            error = None
            try:
                curseur.execute(command)
            except (sgbd_connector.InterfaceError, sgbd_connector.DatabaseError) as erreur:
                error = erreur
            # y-a-t-il eu une erreur ?# ---------------------------------------------------------------------------------
def execute_file_of_commands(sgbd_connector, connexion, sql_filename: str,
                             suivi: bool = False, arrêt: bool = True, with_transaction: bool = True):
    …

    # exploitation du fichier SQL
    try:
        # ouverture du fichier en lecture
        file = open(sql_filename, "r")
        # exploitation
        return execute_list_of_commands(sgbd_connector, connexion, file.readlines(), suivi, arrêt, with_transaction)
    except BaseException as erreur:
        # on rend un tableau d'erreurs
        return [f"Le fichier {sql_filename} n'a pu être être exploité : {erreur}"]
    finally:
        pass

Le paramètre [sgbd_connector] a été utilisé ligne 31 pour préciser le type des exceptions interceptées.

L’exécution du script [any_04] avec les paramètres [mysql false] donne les résultats suivants :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
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

précédentsommairesuivant

Licence Creative Commons
Le contenu de cet article est rédigé par Serge Tahé et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2020 Developpez.com.