Infoforall

Python 28 : Bases de données

Cette activité propose d'utiliser Python pour gérer des bases de données. Vous pourrez ainsi aller chercher des informations dans celles-ci plutôt que dans des fichiers texte par exemple.

Par contre, avant de faire manipuler les bases de données par Python, il faut déjà les manipuler tout court !

Si vous ne savez pas le faire, je vous laisse voir les activités suivantes (que vous pouvez retrouver dans la partie divers du site :

1 - SQLite

Pendant cette activité, nous allons travaillé avec une base de données SQL un peu particulière : SQLite.

Son gros avantage est que cette base de données ne nécessite pas de serveur indépendant : pas besoin de créer un serveur Apache pour permettre l'accès à vos données. On utilise donc beaucoup cette base lorsqu'il s'agit de réaliser un programme autonome n'ayant pas besoin de communiquer avec l'extérieur. Firefox l'utilise pour gérer ses données internes par exemple.

Lorsque vous réalisez votre programme avec Tkinter par exemple, vous pourrez ainsi stocker vos données dans votre base de données sans à sortir l'usine à gaz.

Cette particularité est due à un élément : il s'agit simplement d'un fichier texte dans lequel nous allons faire des accès. Son avantage est donc également son défaut : ce moteur de bases de données n'est pas aussi performant que les autres pour de grandes quantités d'informations. Mais bon, pas d'inquiétude, les performances restent très bonnes. D'ailleurs, ce moteur est certainement le moteur le plus utilisé au monde dans la mesure où tous les appareils portatifs utilisent (ou utilisaient) cette structure.

Autre avantage de SQLite pour Python : le module sqlite3 est inclus de base dans Python. Pas besoin d'utiliser pip pour l'utiliser.

Pour le moteur MySQL, il faudra télécharger avec pip le module mysqlclient par exemple. Le code est donc moins facilement transferable car il faudra vérifier l'importation du module avant de lancer le code. Avec SQLite, vous êtes certain en Python (3 du moins) que votre code sera compréhensible sur n'importe quelle machine utilisant les Pythonneries.

Nous allons commencer par créer et lire naïvement une base de données et y rentrer deux tables :

Voici le code naif pour créer la base de données :

#!/usr/bin/env python

# -*- coding: utf-8 -*-


import sqlite3


creationTableJeux = '''CREATE TABLE jeux (

    id INTEGER PRIMARY KEY AUTOINCREMENT,

    jeu VARCHAR(20),

    description TEXT,

    sortie YEAR,

    editeur VARCHAR(20),

    idSupport INTEGER UNSIGNED NOT NULL,

    FOREIGN KEY (idSupport) REFERENCES supports(idSupport)

);'''


creationTableSupports = '''CREATE TABLE supports (

    idSupport INTEGER PRIMARY KEY AUTOINCREMENT,

    nom VARCHAR(20),

    RAM VARCHAR(20),

    date_sortie DATE

);'''


entreesTableSupports = '''

('Apple II','4 Ko','1977-06-10'),

('Atari 2600','4 Ko', '1977-10-14'),

('C64','64 Ko','1982-08-01');

'''


entreesTableJeux = '''

('Pitfall !', 'Vous êtes Pitfall Harry, un courageux aventurier façon Indiana Jones, perdu au beau milieu d''une jungle très hostile. Vous avez 20 minutes pour trouver un maximum de trésors tout en bravant les multiples dangers de la jungle : éviter les crocodiles, les scorpions, les serpents, le feu, les sables mouvants et les troncs d''arbres qui roulent en votre direction. Vous n''avez que 3 vies, et pas une de plus.', 1982,'Activision',2),

('Space Invaders', 'Vous devez protéger la Terre d''une invasion d''aliens. Heureusement, ils ont un peu de mal à bouger en 3D !', 1980,'Taito',2),

('DECATHLON', '10 épreuves d''athlétisme où l''essentiel du gameplay consiste à secouer le joystick le plus vite possible !', 1983,'Activision',1);

'''


# fichierBDD contient l'adresse de votre fichier SQLite

fichierBDD = "maBaseDeDonnees.sq3"


# objBDD permet de créer un objet Python pour interagir avec le fichier

conn = sqlite3.connect(fichierBDD)

# cur est le curseur de votre base de données

cur = conn.cursor()

# nous pouvons placer en mémoire les requetes SQL avec la méthode execute

cur.execute(creationTableJeux)

cur.execute(creationTableSupports)

cur.execute("INSERT INTO supports (nom,RAM,date_sortie) VALUES "+entreesTableSupports)

cur.execute("INSERT INTO jeux (jeu,description,sortie,editeur,idSupport) VALUES "+entreesTableJeux)

conn.commit()

cur.close()

conn.close()

Quelques remarques avant de commencer :

Analysons un peu ces lignes :

01° Comment se nomme le module à importer pour parvenir à gérer une base de données SQLite ?

02° Quel est le type des variables creationTableJeux et creationTableSupports ?

03° Que semblent-elles contenir ? Que devraient faire ces instructions si on les lancer directement dans une interface gérant SQLite ?

04° Trouver l'instruction qui permet d'executer ces deux requêtes dans le code présenté.

...CORRECTION...

01 : Le module est sqlite3.

02-03 : Ces deux variables sont de simples strings. Elles contiennent des instructions SQL permettant de créer des tables.

04 : on utilise ces deux chaines à l'aide de cur.execute("la_chaine"). La méthode execute() semble donc permettre de faire exécuter des requêtes depuis un code Python.

Comme vous avez pu le voir, nous allons pouvoir utiliser Python pour executer des requêtes SQL classiques. Il faudra simplement rentrer ces requêtes sous forme de strings en tant que paramète de la méthode execute().

Ici, les requêtes ont été tapées à la main mais il conviendrait normalement plutôt de créer des fonctions pour les créer automatiquement en fonction du système de gestion de base de données utilisé, SQLite ou MySQL.

Le défaut de la méthode utilisée ici est que la synthaxe (tapée via un string fixe) n'est correcte que pour SQLite.

Si nous regardons dans le détail, nous voyons ceci :

cur.execute(creationTableJeux)

La méthode execute qui permet visiblement d'exécuter du code SQL fourni en argument doit s'appliquer sur un objet nommé cur. Qu'est-ce que c'est que ça ?

05° Sauvegarder votre fichier py dans un dossier de travail vierge. Il ne contient donc pas d'autre fichier que votre fichier-code Python.

votre dossier de travail

creationBDD.py

06° Lancer le code. Vous devriez constater qu'un fichier nommé maBaseDeDonnees.sq3 vient d'apparaitre à côté de votre fichier Python : ce fichier unique contient votre base de données SQLite !

votre dossier de travail

creationBDD.py

maBaseDeDonnees.sq3

Regardons maintenant comment ce code fonctionne dans le détail.

Création de l'objet-connexion vers la base de données

# fichierBDD contient l'adresse de votre fichier SQLite

fichierBDD = "maBaseDeDonnees.sq3"


# objBDD permet de créer un objet Python pour interagir avec le fichier

conn = sqlite3.connect(fichierBDD)

On donne le nom du fichier voulu dans la variable fichierBDD.

On crée alors un objet conn (pour connexion) en utilisant la méthode connect issue de sqlite3. On fournit en argument la localisation et le nom du fichier qui contiendra la base de données. Cette objet va nous permettre d'agir sur la base de données en lecture et en écriture.

Création de l'objet-curseur

# cur est le curseur de votre base de données

cur = conn.cursor()

Un curseur est un moyen efficace d'agir sur la base de données en asynchrone : on enregistre dans le curseur les modifications qu'on veut réaliser mais les modifications ne seront réellement intégrées à la base de données que lorsqu'on lancera l'ordre de le faire. On pourra ainsi annuler les modifications si on se rend compte d'une erreur ou d'une incompatibilité.

L'objet cur est crée en agissant sur l'objet conn avec la méthode cursor.

Utilisation du curseur pour agir sur la base de données

# nous pouvons placer en mémoire les requetes SQL avec la méthode execute

cur.execute(creationTableJeux)

cur.execute(creationTableSupports)

cur.execute("INSERT INTO supports (nom,RAM,date_sortie) VALUES "+entreesTableSupports)

cur.execute("INSERT INTO jeux (jeu,description,sortie,editeur,idSupport) VALUES "+entreesTableJeux)

conn.commit()

On retrouve les utilisations de la méthode execute qui permet d'enregistrer les requêtes SQL dans l'objet cur.

Le plus important est certainement la dernière instruction : conn.commit() : avec cette instruction, vous enregister dans la base de données les modifications qui avaient été stockées dans l'objet-curseur cur qui fait partie de l'objet-connexion conn.

Fermeture des liaisons

cur.close()

conn.close()

On peut faire disparaitre le curseur avec la méthode close. Si vous voulez annuler les modifications, il suffit donc de fermer le curseur et d'en ouvrir un autre. En tout cas, il ne faut pas lancer votre commit.

Pour faire de même avec la liaison avec la base de données, il suffit d'appliquer la méthode close sur l'objet-connexion conn.

Nous avons maintenant une base de données fonctionnelle. Nous allons tenter de l'ouvrir avec un autre script.

2 - Lecture d'une base de données avec Python

Nous allons créer un nouveau script nommé par exemple lectureBDD.py qui va permettre d'aller lire votre base de données. La structure de vos fichiers sera donc :

votre dossier de travail

creationBDD.py

lectureBDD.py

maBaseDeDonnees.sq3

Voici le code utilisé :

#!/usr/bin/env python

# -*- coding: utf-8 -*-


import sqlite3


# fichierBDD contient l'adresse de votre fichier SQLite

fichierBDD = "maBaseDeDonnees.sq3"


# objBDD permet de créer un objet Python pour interagir avec le fichier

conn = sqlite3.connect(fichierBDD)


# cur est le curseur de votre base de données

cur = conn.cursor()


# nous pouvons placer en mémoire les requetes SQL avec la méthode execute

cur.execute("SELECT * FROM jeux")

print("\nLes jeux\n")

print(type(cur))

for entree in cur:

    print(entree)

    print(type(entree))

cur.execute("SELECT * FROM supports")

print("\nLes supports\n")

for entree in cur :

    print(entree)

    print(type(entree))

cur.close()

conn.close()

input("pause")

07° Lancer le code pour vérifier qu'il fonctionne. Quel est le type de l'objet cur ?

08° Comment est-on parvenu à sélectionner l'ensemble des entrées de la table jeux ?

09° Pourquoi peut-on dire que l'objet cur est itérable ?

10° Dans quel type de structure de données sont stockées les entrées placées dans cur ?

...CORRECTION...

07 : C'est un objet de type Cursor, qu'on trouve dans le module sqlite3.

08 : La requête SELECT * veut dire SELECT ALL.

09 : C'est un itérable car on peut lire ses entrées en utilisant une boucle FOR.

10 : les entrées sont stockées sous forme d'un tuple : on a des parenthèses. Les entrées sont donc non mutables pour l'instant.

11° Pourquoi ne pas avoir utiliser la méthode commit() ici ?

...CORRECTION...

11 : Nous sommes en mode lecture : aucune modification de la table n'est a transmettre définitivement.

Le problème est qu'on perd les informations à la fermeture pour l'instant :

12° Rajouter le code suivant après la première mise en pause :

for entree in cur :

    print(entree)

    print(type(entree))

cur.close()

conn.close()

input("pause")

Vous devriez obtenir une erreur de type :

sqlite3.ProgrammingError: Cannot operate on a closed cursor.

En gros, on ne peut pas travailler sur le curseur une fois fermé. Pourtant, il n'est pas nécessaire de devoir garder la liaison ouverte en permanence : il suffit de stocker les entrées dans une autre variable

Comment ?

Si vous voulez placer le tout dans un tuple (et obtenir ainsi un tuple contenant des tuples), vous pouvez utiliser la fonction native :

mes_entrees = tuple(cur)

Attention :Ce code est bien entendu à placer avant de fermer ou remodifier le curseur !

Vous pourriez aussi utiliser une méthode propre de sqlite3 : la méthode fetchall() :

mes_entrees = cur.fetchall()

Enfin, si vous voulez une liste de tuples, il suffit de se souvenir de la fonction native qui fait cela :

mes_entrees = list(cur)

L'intérêt des entrées stockées elles-même sous forme de tuples, c'est qu'on ne peut ainsi pas les modifier par erreur : si vous voulez les modifier, il faudra transformer l'entrée voulue en list, la modifier puis la retransformer en tuple par exemple.

3 - Insertion de données

Si vous avez compris le principe, nous allons ouvrir la base, créer les objets conn et cur. Il suffit ensuite d'utiliser la méthode execute en fournissant un argument string avec une requête du type "INSERT INTO ..."

13° Créer un fichier rajout.py qui va permettre de rajouter des éléments dans la table :

#!/usr/bin/env python

# -*- coding: utf-8 -*-


import sqlite3


# fichierBDD contient l'adresse de votre fichier SQLite

fichierBDD = "maBaseDeDonnees.sq3"


# objBDD permet de créer un objet Python pour interagir avec le fichier

conn = sqlite3.connect(fichierBDD)


# cur est le curseur de votre base de données

cur = conn.cursor()


# nous pouvons placer en mémoire les requetes SQL avec la méthode execute

cur.execute('''INSERT INTO jeux(jeu,sortie,idSupport)

VALUES ('Super nouveau jeu','2017',2);

''')

conn.commit()

cur.close()

conn.close()

14° Pourquoi le commit() est-il obligatoire ici ?

Dernière chose : laisser les utilisateurs rentrer des données via des champs qu'ils remplissent eux-même ouvre à des failles de sécurité. Elle se nomme SQL injection. Il vaut donc mieux vérifier l'intégrité des données avant de lancer la requête. Ou alors, utiliser une méthode qui le gère.

Pour cela, il faut rentrer vos données dans un tuple puis utiliser le code suivant :

#!/usr/bin/env python

# -*- coding: utf-8 -*-


import sqlite3


# creation des tuples de données

t1 = ('jeu 1', '1980', 1)

t2 = ('jeu 2', '1981', 2)

t3 = ('jeu 3', '1982', 3)

nouvellesDonnees = (t1t1,t1t2,t1t3)


# fichierBDD contient l'adresse de votre fichier SQLite

fichierBDD = "maBaseDeDonnees.sq3"


# objBDD permet de créer un objet Python pour interagir avec le fichier

conn = sqlite3.connect(fichierBDD)


# cur est le curseur de votre base de données

cur = conn.cursor()


# nous pouvons placer en mémoire les requetes SQL avec la méthode execute

for tup in nouvellesDonnees :

    cur.execute("INSERT INTO jeux (jeu,sortie,idSupport) VALUES(?,?,?)", tup)


conn.commit()

cur.close()

conn.close()

15° Créer un nouveau fichier Python nommé par exemple rajout2.py et vérifier qu'il fonctionne.

En analysant le code :

16° Pourquoi voit-on que nouvellesDonnees est un tuple ?

17° Par quoi sont manifestement remplacés les ? présents dans le string de l'execute ?

Voici pour la gestion de la base de données.

18° S'il vous reste du temps, vous pouvez créer une jolie interface Tk qui affiche le contenu de la base de données, qui propose de rajouter des entrées ou d'en supprimer.