seance3_4h.md
-
Derrode Stéphane authoredDerrode Stéphane authored
- BE #3 : Exceptions et Base de données SQL
- 1. Mini-tutoriel sur la base Hotellerie.db (45 min.)
- 1.1 DB browser for SQLite (15 min.)
- 1.2 Quelques requêtes en Python (15 min.)
- 1.3 La gestion des exceptions (15 min.)
- 2. Classe HotelDB (75 min.)
- 2.1 Requête en lecture (30 min.)
- 2.2 Requête en écriture (45 min.)
- 3. Requêtes libres (120 min. et +)
- Liens vers des Bdds sqlite
- Quelques conseils
Sommaire
- BE #3 : Exceptions et Base de données SQL
BE #3 : Exceptions et Base de données SQL
L'objectif principal de ce BE concerne l'utilisation des exceptions pour améliorer la robustesse d'un code. Pour expérimenter ce concept clé, nous nous servirons du prétexte de la manipulation de bases de données à l'aide de requêtes SQL (Structured Query Language), écrites en Python. Ce BE est décomposé en trois parties:
- La première partie (durée: 45 min.) présente quelques commandes élémentaires pour interroger une base SQL à partir de Python;
- La seconde partie (durée: 75 min.) permet de découvrir comment manipuler une base de données SQL en Python orienté objet.
- La troisième partie (durée: 120 min. et +) vous engage dans un travail plus personnalisé, pour mettre à profit vos connaissances sur la gestion des exceptions, la librairie graphique matplotlib et bien sûr le langage d'interrogation de bases SQL.
Ce BE fera l'objet d'un compte-rendu (CR), seul ou en binôme. Avant de commencer, veuillez prendre connaissance des consignes concernant le rendu du travail (à respecter scrupuleusement) qui se trouvent dans le fichier consignes_BE#3.md (dans le même répertoire que cet énoncé). Les critères de notations y sont donnés.
1. Mini-tutoriel sur la base Hotellerie.db (45 min.)
Le système de gestion de base de données qui sera utilisé durant ce BE est SQLite. Ce système très simple fonctionne en stockant une base de données dans un fichier d'extension .sqlite. La base de tests que nous allons utiliser dans cette partie (hotellerie.db) est disponible au même endroit que cet énoncé. Son schéma est le suivant :

La base est composée de 5 tables, ces tables étant composées d'un nombre variable de champs. Les champs soulignés représentent les clés primaires (ou primary key (PK) en anglais) de chaque table. En particulier, la clé primaire de la table chambre est composée des attributs numchambre et numhotel (cette dernière étant la clé primaire de la table hotel).
Remarque : Vous trouverez ici une vidéo qui montre comment utiliser draw.io pour créer un diagramme entité-association. Il NE vous est PAS demandé de dessiner le diagramme de cette base de données.
1.1 DB browser for SQLite (15 min.)
Toutes les opérations sur une base de données de ce type peuvent être effectuées en Python via les classes et les méthodes présentes au sein du module sqlite3. Pour manipuler de manière interactive le contenu de la base (créer, supprimer ou modifier des tables et des enregistrements, effectuer des requêtes SQL...), il existe des outils adaptés. L'outil retenu dans le cadre de ce BE s'appelle DB Browser for SQLite
. C'est un logiciel libre qui existe pour toutes les plate-formes : Windows, MacOs, nombreuses distributions Linux et Unix...
- Téléchargez et installez DB Browser for SQLite en suivant les instructions d'installation adaptées à votre système d'exploitation.
- Avec ce logiciel, ouvrez la base hotellerie.db et naviguez dans les tables (onglet
Structure de la Base de Données
) et les enregistrements (ongletParcourir les données
) pour prendre connaissance de la base (telle qu'elle est schématisée ci-dessus).

- Dans l'onglet
Exécuter le SQL
, lancez la requête suivante
SELECT nom, ville
FROM hotel;
La réponse apparaît sous forme de 12 lignes. Ça vous rappelle des choses ? Si non, alors voici quelques pointeurs pour vous rafraîchir la mémoire :
- cours tutoriel sur SQL
- SQL : sélection, jointure, regroupement, filtre
- et tant d'autres...
1.2 Quelques requêtes en Python (15 min.)
Attention Avant de lancer un requête sur la bdd avec Python, il est fortement conseillé de fermer DB Browser for SQLite
, sinon vous pourriez soit avoir un plantage de votre programme, soit détruire la bdd (auquel cas il vous suffirait de la télécharger à nouveau).
Nous allons à présent chercher à reproduire la requête ci-dessus en utilisant Python et le package sqlite3. C'est une librairie objet dont la documentation fournit une description des classes et des méthodes disponibles. Suivez le guide...
Le squelette typique d'un tel programme s'écrit :
import sqlite3
if __name__ == '__main__':
conn = sqlite3.connect('hotellerie.db') # connexion à la bdd
# travail sur la bdd à partir du connecteur
...
conn.commit() # pour enregistrer les éventuels changements
conn.close() # pour fermer proprement l'accès à la base
Voici le code Python permettant d'obtenir la réponse à la requête précédente :
import sqlite3
if __name__ == '__main__':
conn = sqlite3.connect('hotellerie.db')
curseur = conn.cursor() # objet permettant de faire des requêtes
curseur.execute("SELECT nom, ville FROM hotel;") # requête SQL
ligne1 = curseur.fetchone() # recupère la 1ère ligne du résultat de la requête
print('ligne1 =', ligne1)
ligneAll = curseur.fetchall() # recupère toutes les lignes du résultat de la requête
print('ligneAll =', ligneAll)
conn.close()
Remarques
- La commande
conn.commit()
n'est pas nécessaire ici puisque le script est une requête en lecture ; elle ne modifie donc pas la bdd. - Notez que la méthode
fetchone()
retire le résultat de la liste des résultats. Pour preuve: ce résultat n'est pas présent suite à l’affichage dufetchall()
.
Copiez et exécutez ce programme ; le résultat se présente sous forme d'un tuple, ou sous forme d'une liste de tuples. Ainsi la commande suivante imprime le nom du premier hôtel qui apparaît dans la liste des résultats de la requête :
print(ligneAll[0][0])
Voici un usage intéressant à étudier :
import sqlite3
if __name__ == '__main__':
conn = sqlite3.connect('hotellerie.db')
curseur = conn.cursor()
for ligne in curseur.execute("SELECT * FROM hotel WHERE etoiles=3"):
print('ligne=', ligne)
conn.close()
1.3 La gestion des exceptions (15 min.)
Les quelques lignes de code que nous venons d'étudier peuvent être à l'origine de nombreux problèmes lors de l'exécution :
- la base de données est introuvable;
- le nom des tables ou des champs dans la requête sont erronés;
- ...
Voyons maintenant comment intercepter les exceptions lancées dans ces cas de figure par les méthodes de la librairie sqlite3
. Pour cela, veuillez d'abord copier et exécuter le programme suivant :
import sqlite3
if __name__ == '__main__':
try:
conn = sqlite3.connect('hotellerie.db')
curseur = conn.cursor()
curseur.execute("SELECT nom, ville FROM hotel;")
print(curseur.fetchall())
except Exception as err: # interception d'une exception quelconque
print('type exception: ', type(err).__name__)
print('err: ', str(err))
finally: # fermeture de la base dans tous les cas
conn.close()
A priori, ce programme ne lance pas d'exception. Modifiez-le pour faire apparaître les 2 problèmes énoncés ci-dessus. Pour cela :
- Changez le nom de la bdd dans le programme, p. ex. impossible.db. Comment interprétez-vous le message d'erreur en observant le contenu de votre répertoire de travail ?
- Revenez au nom correct du fichier : hotellerie.db. Testez alors le changement de nom des tables, puis des champs.
On constate que, dans chaque situation, l'exception OperationalError
est lancée, avec des messages d'information différents qui précisent le type d'erreur. Remplacez alors le code except Exception as err:
par le code except sqlite3.OperationalError as err:
, et vérifiez que cela fonctionne de la même manière que précédemment.
La liste des exceptions lancées par l'usage de commandes sqlite3
est disponible en suivant ce lien. Prenez le temps de lire la description des différentes exceptions. Toutes les exceptions de ce module héritent d'une classe appelée sqlite3.Error (elle-même héritant de la classe de base de gestion des exceptions de Python : Exception).
2. Classe HotelDB (75 min.)
Dans cette partie, nous allons créer un classe HotelDB permettant de réaliser des requêtes de lecture et de mises à jour de la base Hotellerie.db.
2.1 Requête en lecture (30 min.)
Dans un fichier HotelDB.py, commencez à développer la classe permettant de répondre au programme principal suivant, dont l'objectif est d'afficher le nom des hôtels 2 étoiles (notez que le nombre d'étoiles est passé en argument) :
if __name__ == '__main__':
aHotelDB = HotelDB('hotellerie.db')
nbEtoiles = 2
resultat = aHotelDB.get_name_hotel_etoile(nbEtoiles)
print("Liste des noms d'hotel", nbEtoiles, "étoiles : ", resultat)
Le constructeur (méthode __init__(...)) se chargera d'ouvrir une connexion vers la bdd, alors que le destructeur (méthode __del__(...), cf remarque ci-dessous) se chargera de la fermer.
Remarques :
- Pour fermer correctement l'accès à la base de donnée, pensez à implémenter la méthode __del__(...) (vue en cours), qui est appelée automatiquement (et de manière implicite) par Python lors de la destruction des objets de la classe. Typiquement :
def __del__ (self):
self.__conn.close()
- La méthode
get_name_hotel_etoile(...)
se charge de retourner le résultat de la requête, mais ne se charge pas d'en afficher le résultat. L'affichage est laissé au programme principal, ici essentiellement pour contrôler le résultat. - Pensez à intercepter les exceptions de type
sqlite3.OperationalError
, comme vu précédemment, et à renvoyer dans ce cas un résultat sous la forme d'une liste vide.
Améliorations à implémenter :
- Comment se comporte votre programme si on insère cet appel
aHotelDB.get_name_hotel_etoile(-1)
dans le programme principal ? Truc: exception standard ValueError ou AssertionError. - Comment se comporte votre programme si on insère cet appel
aHotelDB.get_name_hotel_etoile("Hello")
? Truc: exception standard TypeError.
Veillez à ce que ces appels erronés renvoient une liste vide tout simplement.
2.2 Requête en écriture (45 min.)
Créer une requête permettant d'ajouter un nouveau client et de renvoyer son identifiant (c'est à dire son numclient). Si le client existe déjà (même nom ET même prénom), la méthode renverra son numclient (on supposera, pour des raisons de simplification, qu'il n'y a pas clients homonymes). Pour cette requête, renseignez-vous sur la commande INSERT INTO
. Attention la clé primaire sera renseignée automatiquement, pas besoin de la stipuler. Notez également que l'attribut curseur.lastrowid permet de récupérer le numclient du nouveau client.
Vérifier que le nouveau client a bien été sauvegardé dans le fichier Hotellerie.db :
- soit en consultant la base avec
DB Browser for SQLite
; - soit en exécutant par 2 fois successives le même programme ; vous devriez alors retrouver le même numéro de client.
Remarques :
- Pensez à quitter le logiciel
DB Browser
avant d'exécuter votre programme. - En cas de destruction de la base
Hotellerie.db
(les requêtes en écriture sont toujours plus dangereuses que les requêtes en lecture !), pensez à la télécharger à nouveau !
3. Requêtes libres (120 min. et +)
Dans cette dernière partie, nous vous invitons à imaginer et implémenter DEUX (2) requêtes originales à partir de la bdd Hotellerie.db, ou de tout autre bdd que vous aurez trouvée sur Internet.
Les résultats de vos requêtes devront faire l'objet d'une représentation graphique (graphe, histogramme, camembert...) en utilisant la librairie Matplotlib (cf les remarques ci-dessous).
Vous pouvez "justifier" la robustesse de vos requêtes en écrivant, dans votre programme principal, plusieurs appels à vos requêtes avec des paramètres farfelus, erronés... Bonus à ceux qui développerons une exception propre !
sqlite
Liens vers des Bdds Voici quelques exemples de sites proposant des bdd SQLite gratuites :
- Le site SQLite tutorial propose un base de données appelée chinook (digital media store), composée de 11 tables.
- Dans le même genre, une base de données très célèbre : Northwind (8 tables).
- Si vous êtes fan des Pokémon, vous pouvez décompresser la base veekun's Pokédex (172 tables).
- Si vous êtes fan de musique, vous pouvez décompresser et utiliser la base musicBrainz (4 tables).
- Une petite base concernant la peinture.
- Beaucoup plus complexe : murder-mystery (9 tables). Lire le site original.
- postuler à Stanford (3 tables).
Vous pouvez également transformer des données du format .csv
(Comma Separated Value) vers le format .sqlite3
, en suivant ce tutoriel video. Toutes les données de data.gouv.fr, par exemple, deviennent alors exploitables pour votre CR...
Au cas où vous opteriez pour une bdd originale, n'oubliez pas d'inclure cette base dans votre archive (si elle n'est pas trop volumineuse), et de préciser dans votre rapport le chemin pour la télécharger.
Quelques conseils
Les deux requêtes attendues doivent être relativement sophistiquées (pas de simples select XX from YY
).
- Si vous optez pour la bdd
Hotellerie.db
, n'hésitez pas à visiter un site de réservation d'hôtels pour trouver des idées de requêtes intéressantes. Attention : le nom d'un hôtel n'est pas une clé primaire ! Plusieurs hôtels portent le même nom. Par contre, il n'existe pas 2 hôtels de même nom dans la même ville. Pensez-y ! - Si vous optez pour une autre base, développez une seconde classe dans un second fichier indépendant (sur le modèle de la classe HotelDB).
Remarques :
- Vous programmerez les représentations graphiques dans le programme principal (et non pas dans la méthode qui traite la requête). En effet, quand on fait une requête sur une base de données, l'affichage graphique ne doit pas être obligatoire. C'est pour cela qu'on sépare la requête de l'affichage de son résultat (qu'il soit au format texte ou au format graphique).
- Usage de Matplotlib : À titre d'exemples, vous trouverez, à côté de cet énoncé, un fichier nommé ex_matplotlib.py. L'exécution de ce script génère 4 figures dans le sous-répertoire figures. Inspirez-vous largement de ce programme pour vos propres figures. Conseil: Évitez de vous lancer dans des requêtes avec des données géographiques, genre
trouver tous les hôtels à moins de 5 kilomètres
car l'usage de cartes géographiques dépasse les attentes de ce qui est demandé ici.