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 :
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 :
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 :
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 :
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 :
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électionnez1.import
sgbd_connectorCeci 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 :
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 :
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