Interagir avec une base de données SQLite3 depuis Python

I. Présentation générale

Pour interagir avec une base de données au format SQLite3 depuis Python, il est nécessaire d'utiliser le module sqlite3. Ensuite, il faut effectuer un certain nombre d'étapes :

  1. Se connecter à la base de données ;
  2. Exécuter la requête ;
  3. Éventuellement, exploiter le résultat de la requête ;
  4. Si la base a été modifiée, valider les changements ;
  5. Se déconnecter de la base

Nous allons maintenant étudier ces différentes parties dans le désordre.

II. Connexion et déconnexion

Pour une base SQLite3, ces 2 étapes sont très simples :

Connexion
      connexion = sqlite3.connect('NomFichierBase.db')
      

La base sera en effet stockée dans un fichier dont le nom est suffixé par ".db"

Déconnexion
      connexion.close()
      

III. Valider les changements

Les bases de données sont des systèmes transactionnels. Cela signifie qu'un programme qui interagit avec une base de données fait ses modifications en mémoire, jusqu'à ce que celles-ci soient validées. Lors de cette validation, si les modifications sont toujours possibles (ce n'est pas forcément le cas, si un autre utilisateur a modifié la base entre temps), elles sont toutes transférées dans la base de données sur le disque. Sinon, aucune n'est effectuée. C'est cette validation par paquet qui constitue une transaction. En Python, les transactions sont validées en appelant la méthode commit() sur l'objet connexion. Chaque commit termine la transaction précédente, puis en commence une autre. La première transaction est initiée lors de la connexion à la base de données.

Bien sûr, si aucune modification n'a été apportée à la base, la phase de validation n'est pas nécessaire. Dans le cas contraire, on valide nos modifications ainsi :

  connexion.commit()
  

IV. Exécuter la requête et exploiter ses résultats

A. Exécuter une requête

Pour exécuter une requête, il faut la passer en paramètre de la méthode execute de l'objet connexion.

1. Création d'une table

Voici par exemple le code d'un programme Python permettant de créer la table Editeur :

import sqlite3

#connexion à la base
connexion = sqlite3.connect('ludotheque.db')
#activation de la vérification des contraintes de clé étrangère
connexion.execute('PRAGMA foreign_keys = ON')

#suppression éventuelle de l'ancienne table
connexion.execute('DROP TABLE IF EXISTS editeur')

#creation de la table Editeur
connexion.execute('''CREATE TABLE editeur (
     idEditeur int primary key not null,
     nomEditeur text not null,
     nationaliteEditeur text)''')

#validation
connexion.commit()

#déconnexion
connexion.close()
  

2. Ajout de données dans une table

Pour ajouter des données dans une table, on peut utiliser la méthode execute() présentée précédemment :

import sqlite3

connexion = sqlite3.connect('ludotheque.db')
connexion.execute('PRAGMA foreign_keys = ON')

connexion.execute('''INSERT INTO editeur
     VALUES(1, 'Days of wonder', 'Française')''')
connexion.commit()
connexion.close()
  

Commencez par exécuter le programme précédent en mettant en commentaire le commit et vérifiez le contenu de la table (avec le programme sqlite3 par exemple) : vous constaterez que le tuple n'a pas été ajouté. Recommencez ensuite en décommentant le commit

Lorsqu'on désire insérer plusieurs données dans une table, on peut utiliser avantageusement la méthode executemany : Celle-ci prend 2 paramètres : une requête paramétrée (des '?' signalent les zone paramétrées) et une liste de tuples : la requête est exécutée pour chaque tuple de la liste, en remplaçant le i-ème point d'interrogation par la i-ème composante du tuple. Exemple :

import sqlite3

connexion = sqlite3.connect('ludotheque.db')
connexion.execute('PRAGMA foreign_keys = ON')

listeEditeurs = [
    (2, 'EggertSiele', 'Allemande'),
    (3, 'Iello', 'Française')
    ]
connexion.executemany("INSERT INTO editeur VALUES(?, ?, ?)", listeEditeurs)

connexion.commit()
connexion.close()

B. Exploiter les résultats

Lorsqu'on effectue une requête d'interrogation, c'est a priori pour récupérer les résultats ! Il se trouve que la méthode execute présentée précédemment renvoie un objet cursor qui est itérable. On peut donc aisément parcourir les résutats d'une requête ainsi :

import sqlite3

connexion = sqlite3.connect('ludotheque.db')
connexion.execute('PRAGMA foreign_keys = ON')

curseur = connexion.execute("SELECT * FROM Editeur")

for tuple in curseur:
    print(tuple)
    
connexion.close()

Pour récupérer les valeurs pour les différents champs de chaque tuple, on peut les convertir en liste ainsi :

import sqlite3

connexion = sqlite3.connect('ludotheque.db')
connexion.execute('PRAGMA foreign_keys = ON')

curseur = connexion.execute("SELECT * FROM Editeur")

for tuple in curseur:
    donnee = list(tuple)
    print(donnee[1])
    
connexion.close()

Bien sûr, dans un tel cas, comme seuls les noms des éditeur importent, il es préférable d'avoir une requête avec une projection plus sélective :

import sqlite3

connexion = sqlite3.connect('ludotheque.db')
connexion.execute('PRAGMA foreign_keys = ON')

curseur = connexion.execute("SELECT nomEditeur FROM Editeur")

for tuple in curseur:
    donnee = list(tuple)
    print(donnee[0])
    
connexion.close()

C. Risque d'injection et requêtes paramétrées

Une requête simple peut également être paramétrée, comme une requête multiple. Voici un exemple d'un programme permettant de faire une recherche des éditeurs par nationalité :

import sqlite3

connexion = sqlite3.connect('ludotheque.db')
connexion.execute('PRAGMA foreign_keys = ON')

nationalite = input("Nationalité recherchée ? ")

curseur = connexion.execute('''SELECT nomEditeur
                               FROM editeur
                               WHERE upper(nationaliteEditeur) = upper(?)
                               ORDER BY nomEditeur''',
                            (nationalite,))

for tuple in curseur:
    print(list(tuple)[0])
    
connexion.close()

Lorqu'une requête est paramétrée par une information issue (directement ou indirectement) de l'utilisateur, il est hautement préférable d'utiliser les requêtes paramétrées plutôt que la concaténation de chaînes de Python pour éviter des attaques par Injection SQL (voir la page wikipedia sur le sujet).