Skip to content
Snippets Groups Projects
Select Git revision
  • master default protected
  • vS5_2020-2021
2 results

seance3_4h.md

Blame
  • Forked from Derrode Stéphane / INF-TC2
    20 commits behind the upstream repository.

    Sommaire

    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:

    1. La première partie (durée: 45 min.) présente quelques commandes élémentaires pour interroger une base SQL à partir de Python;
    2. La seconde partie (durée: 75 min.) permet de découvrir comment manipuler une base de données SQL en Python orienté objet.
    3. 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 (onglet Parcourir 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 du fetchall().

    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 !

    Liens vers des Bdds sqlite

    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.