Python et NoSQL




MongoDB

Dans la famille des bases de données non relationnelle, MongoDB représente les données sous un format qui s’apparente à celui des dictionnaires de Python, le format JSON.
Rappelons que le système de bases de données non relationnelles NoSQL (Not only SQL) concerne des bases dans lesquelles les données peuvent être représentées de diverses façons, entre autres par un couple clé/valeur dont la valeur peut être un objet complexe.

démarrage de MongoDB

L’installation s’effectue via le site https://docs.mongodb.com/manual/installation/ pour tous les types de systèmes d’exploitation et les versions disponibles (Enterprise/Community).
En offrant des fonctionnalités avancées, certaines versions sont payantes.
Sous Windows, l’installation s’effectue en téléchargeant un dossier à dézipper dans le répertoire de votre choix: le plus simple est de créer un répertoire MongoDB (sur C: pour faire simple). A partir de ce répertoire et en tapant DIR sur l’invite de commande, on constate l’existence d’un sous-dossier Bin: il contient l’exécutable mongod qu’il faut lancer pour établir une connexion au port logique 27017.
Pour résumer,

  • A partir du dossier bin du répertoire d’installation (ici C:\MongoDB\bin), on lance mongod
  • Les messages affichés lors de la tentative de connexion se terminent par

Une interface graphique de base: Robo 3T (anciennement RoboMongo)

Dans ce chapitre qui se limite aux notions de base, nous faisons le choix d’utiliser Robo 3T, car son installation est très simple à partir de https://robomongo.org/
Une fois Robo 3T installé, son activation ouvre une fenêtre qui rappelle le port sur lequel est connecté votre base de données MongoDB (localhost:27017) ainsi que l’identifiant de votre dernière connexion: celui-ci peut être changé en cliquant droit sur le nom de cette connexion et sélectionner edit pour (éventuellement) modifier les paramètres de connexion: base de données, port, etc.
 


 
En pressant le bouton Connect, l’interface graphique de Robo 3T fait apparaître, sur la gauche, le récapitulatif des bases de données accessibles. dans notre cas, il s’agit de Pandas Test, onebase et twobase, le dossier config étant un dossier système.
 

 
Chaque dossier a trois rubriques: Collections, Functions et Users

Collections

Dans les bases non relationnelles, les collections remplacent les tables.
Chaque enregistrement est appelé document: son format est extensible, c’est à dire que les documents peuvent avoir des structures différentes, comportant un nombre de champs (ou clés) variable.
Les formats dans lesquels sont enregistrés les documents sont principalement XML et JSON: dans cette présentation, nous utilisons JSON.
Prenons l’exemple d’une base contenant les données relatives à des CV de jeunes diplômés.
Une table SQL devra au minimum contenir quatre colonnes (Nom, Prénom, Date de naissance, Diplôme) comme dans la table CVcandidats ci-dessous
 

 
Si les candidats mentionnent dans leur CV la pratique d’un sport, d’un hobby, ou d’un engagement humanitaire, il faut créer trois colonnes supplémentaires qui contiendront beaucoup de champs non renseignés
 

 
Il est de plus en plus courant que les candidats possèdent plusieurs diplômes et/ou pratiquent plus d’un sport par exemple, ce qui rend l’enregistrement dans une table SQL plus problématique.

A l’opposé, le format JSON permet une certaine flexibilité dans l’enregistrement des documents. La syntaxe de JSON est dérivée de celle que JavaScript utilise pour décrire des objets. Les développeurs Python quant à eux reconnaîtront la syntaxe des dictionnaires (type dict) dans la définition des documents JSON à quelques nuances près. Si les données sont identifiées par des couples clé:valeur, en JSON,

  • les clés sont toujours sous format texte (str) encadrés par des guillemets (les apostrophes sont proscrits)
  • le signe ‘:’ est obligatoire (pas de ‘=’)
  • chaque document a obligatoirement un numéro d’identification qui, à défaut d’être donné par l’utilisateur, est imposé par la machine

Ci-dessous, trois documents de la collection CVcandidats, reprenant les mêmes informations que les enregistrements 2,3 et 5 de la table du même nom
 

 
Dans cet exemple, on voit que les documents n’ont pas le même nombre de clés et que certaines valeurs sont, si besoin, des listes: à la place des listes, on pourrait avoir tout aussi bien d’autres documents.
Dans le cas de SQL, on préfère généralement créer d’autres tables que d’ajouter des colonnes qui seront partiellement vides. Pour faire le lien entre tables, on utilise des clés secondaires (foreign key). Cela nécessite une modélisation, passant par un schéma relationnel (ou diagramme)entre les tables. C’est un peu fastidieux, mais un diagramme pertinent permet d’éviter les redondances tout en stockant l’ensemble des données.
 
Exemple ci-dessous d’une bibliothèque municipale qui veut procurer à ses membres cotisants des informations quant aux ouvrages disponibles et, bien entendu, contrôler les emprunts et restitutions de livres. Un schéma de données pertinent pourra ressembler à ça:
 

 
Dans notre exemple, une table Biographie a été créée pour chaque auteur avec un nombre d’enregistrements qui varie selon l’auteur. Cependant, si l’auteur est traduit en plusieurs langues et, fait plus rare, écrit en plusieurs langues, il faudra à nouveau modifier le schéma initial et créer de nouvelles tables.

Cette modélisation, n’est donc pas le souci des utilisateurs de NoSQL (bien qu’elle reste possible).
Il y a un revers à cette souplesse: les bases de données relationnelles sont conçues pour éviter les redondances et permettre, à l’aide de requêtes standard sur les clés d’enregistrement de filtrer ou regrouper des informations provenant de diverses tables. A l’opposé, les données stockées dans des documents sont hiérarchisés: les informations que l’on recherche pouvant être « enfouies » au milieu d’autres données, il faut concevoir des requêtes spécifiques pour les extraire.
De plus, certaines valeurs peuvent être reprises dans plusieurs documents (données redondantes), ce qui n’est pas optimal pour une base de données.

Création d’une base MongoDB en Python

Commencer par établir une connexion avec le port 27017 en tapant en ligne de commande mongod

Repertoire de travail\Mongodb\bin\mongod

Par exemple, si vous avez choisi d’installer MongoDB à partir du répertoire racine de votre disque dur,

C:\Mongodb\bin\mongod

Vous aurez besoin d’importer le package MongoClient de la distribution pymongo, déjà installé si vous travaillez avec la distribution Anaconda: sinon, tapez dans votre répertoire de travail
pip install pymongo

Le script Python commence par créer une instance de votre connexion, c


 
Cette connexion est censée donner accès à plusieurs bases, chaque base étant un élément de c. Donc, en initialisant la liste, on crée une première base.
Idem pour les collections de cette base.
Dans l’exemple ci-dessous, le nom donné à la (première) base est ‘myDB‘ et la (première) collection ‘CVcandidats‘ sur le modèle de la table SQL du même nom:
 

 
A ce stade là, la base n’existe pas encore, car il ne peut y avoir de base vide dans MongoDB. On veut loger 3 documents (‘Dupont’, ‘Dutilleul’ et ‘Destouches’) dans la collection CVcandidats.
Chaque document, qui a la structure d’un dictionnaire (type dict), est d’abord placé dans un tuplet que l’on parcourt pour enregistrer chaque item dans CVcandidats via la méthode insert_one.

 
Il est possible d’insérer plusieurs documents à la fois, par exemple dans une liste, à l’aide de la méthode insert_many, et même d’imposer les numéros d’identification

 
Pour l’affichage des documents, RoboMongo donne le choix entre trois vues en activant l’une des trois icônes en haut à droite de la zone d’affichage
 
En choisissant le mode texte (3ème icône), les trois derniers documents enregistrés s’affichent comme suit:
 

 

Requêtes en Python sur une collection

 
Pour récupérer les éléments d’une collection, on exécute la méthode find qui retourne un objet de type cursor

 
L’objet retourné s’apparente, comme son type le laisse supposer, à un itérateur (curseur et itérateur sont un peu synonymes).
Si l’on veut filtrer les éléments d’une collection, par exemple sélectionner les membres de la collection CVcandidats pratiquant le handball, on écrira (le test try étant ici obligatoire, les documents NoSQL n’ayant pas forcément des clés identiques)

 

 
L’objet retourné par la méthode find étant assimilable à un itérateur, il ne renvoie pas de séquence mais se contente de lister les éléments de la collection. Si l’on veut conserver en mémoire la totalité des éléments de la liste pour effectuer des traitements ou produire des statistiques dessus, on peut les enregistrer dans une séquence de type list ou tuple, par exemple.
La sélection des éléments de CVcandidats pratiquant le handball pourra s’écrire
 

 
La conversion au type list permet aussi de récupérer les documents dans un objet DataFrame
 

 
On peut aussi filtrer les documents en fonction du format de certaines valeurs en utilisant les expressions régulières (regex) {« $regex »: « expression regex »}. Par exemple, pour afficher les noms commençant par ‘Du’, on passera à la méthode find la valeur {« $regex »: « ^Du »} à la clé « Nom »