grimboite/articles/dev/2013-03-06-py-peewee.md

7.3 KiB

Peewee & Flask

J'étais parti sur un article sur les API Rest en utilisant Flask et Peewee, mais vu la relative complexité de ces micro-frameworks pour sortir un code propre et complètement fonctionnel, je me suis dit qu'il serait bien d'avoir une petite introduction définissant les bases d'un projet.

Flask est un micro-framework (en Python) qui propose un ensemble de fonctions essentielles pour mettre une application Web en place, et dans laquelle tout élément peut être facilement modifié.

Peewee, pour sa part, est un ORM (également écrit en Python). C'est la couche d'abstraction entre le code source et la base de données, permettant de construire directement le schéma SQL à partir du code pur. En gros, c'est comme n'importe quelle autre définition de classe, sauf que l'ORM assure la persistance et s'occupe (normalement) du lien entre le SGDB, quel qu'il soit (Oracle, MSSQL, MySQL, ...), et votre modèle de données.

Flask-peewee finalement est un wrapper entre le framework Web Flask et l'ORM peewee, mais y ajoute également toute une série de fonctionnalités très utiles pour démarrer rapidement: des pages d'administration <http://flask-peewee.readthedocs.org/en/latest/admin.html>_ pour gérer les instances, une gestion des utilisateurs et des droits, une interface REST <(http://flask-peewee.readthedocs.org/en/latest/rest-api.html>_, ...

Pour l'exemple ci-dessous, j'ai structuré mon projet comme suit:

gwift.py    # les vues, le modèle de données, la partie admin, ...
config.py   # la configuration de l'application en elle-même
templates/  # les templates html, leurs dépendances, ...
    css/
    img/
    js/

Les templates ne seront utilisés que pour l'utilisation concrète de l'application. En attendant d'arriver à cette étape, il est possible de construire quelque chose d'utilisable, en se basant uniquement sur la définition du modèle et sur la partie d'administration.

Pour la première phase, on va donc définir un modèle, et mettre en place la partie d'administration de l'application. L'ORM de Peewee n'est pas compliqué à mettre en place, il faut juste respecter les étapes dans le bon ordre:

Définition de la configuration

Je l'ai mise dans un fichier à part, pour plus de clarté. Ce fichier sera ensuite chargé à l'instanciation de l'application Flask, grâce aux imports suivants:

import config

app = Flask(__name__, static_folder='static', static_url_path='')
app.config.from_object(config)

db = Database(app)

Le fichier config.py se présente de la manière suivante :

DATABASE = {
    'name' : 'example.db',
    'engine' : 'peewee.SqliteDatabase',
}

SECRET_KEY = 'my_reeeeeeally_secret_key'

DEBUG = True

L'avantage, c'est qu'il suffit de modifier le paramètre DATABASE pour changer la connexion vers la base (SQLite, Postgres, MySQL, ...).

Le modèle

On va définir quelques classes dans le fichier gwift.py, et les lier à la base de données définies ci-dessus.

from flask import Flask, render_template
from flask_peewee.rest import RestAPI, RestResource, UserAuthentication
from flask_peewee.auth import Auth
from flask_peewee.db import Database

from peewee import SqliteDatabase, Model, CharField, DateField, ForeignKeyField, DateTimeField, BooleanField, DecimalField, IntegerField
from flask_peewee.admin import Admin

import config

app = Flask(__name__, static_folder='static', static_url_path='')
app.config.from_object(config)

db = Database(app)

auth = Auth(app, db)


class WishList(db.Model):
    name = CharField()
    description = CharField()
    validity_date = DateField()
    user = ForeignKeyField(auth.User, related_name='wishlists')

    class Meta:
        order_by = ('name',)


class Item(db.Model):
    name = CharField()
    description = CharField(max_length=2000, null=True)
    url = CharField()
    price = DecimalField(max_digits=10, decimal_places=2)
    wishlist = ForeignKeyField(WishList, related_name='items')
    numberOfParts = IntegerField()


class Part(db.Model):
    user = ForeignKeyField(auth.User, related_name='gifts')
    item = ForeignKeyField(Item, related_name='parts')

On a trois classes: WishList, Item, et Part. Chaque champ appartenant à ces classes est typé, et la syntaxe se rapproche énormément de celle de l'ORM de Django <https://docs.djangoproject.com/en/dev/topics/db/models/>. Les attributs Meta sont utilisés pour donner un ordre par défaut lors d'une requête, rien de plus. D'autres attributs <http://peewee.readthedocs.org/en/latest/peewee/models.html#model-options-and-table-metadata> sont disponibles.

Chaque classe hérite de db.Model, afin de spécifier une base de données par défaut. On pourrait tout à fait se passer de cet héritage, mais il faudrait alors ajouter la ligne database = db dans les Meta pour chaque nouvelle classe, ce qui pète un peu le principe de DRY <https://fr.wikipedia.org/wiki/Ne_vous_r%C3%A9p%C3%A9tez_pas>_.

Je triche un peu en ajoutant déjà maintenant la configuration de l'authentification de l'application: cela me permet de lier les instances de WishList et de Part à un utilisateur déjà existant (et cela force les utilisateurs à avoir un compte avant de commencer à interagir avec les données).

On crée ensuite les tables au moment du lancement du script, dans la méthode main():

.. code-block:: python

if __name__ == '__main__':
    auth.User.create_table(fail_silently=True)
    WishList.create_table(fail_silently=True)
    Item.create_table(fail_silently=True)
    Part.create_table(fail_silently=True)

Pour tester tout ça, lancez un interpréteur Python dans un shell, depuis le répertoire où se trouvent tous vos fichiers :

.. code-block:: python

>>> import models
>>> models.create_tables()
>>> from datetime import datetime
>>>
>>> u = models.User()
>>> u.username = 'root'
>>> u.password = 'admin'
>>> u.email = ''
>>> u.join_date = datetime.now()
>>> u.save()

Et pour vérifier que cela fonctionne bien :

>>> users = models.User.select()
>>> users.count()
1
>>> users[0].username
u'root'

Des contraintes <http://peewee.readthedocs.org/en/latest/peewee/models.html#indexes-and-unique-constraints>_ (unicité, indexes, ...) pourraient être ajoutées sur ce modèle pour réellement correspondre à un besoin, mais la base est là et fonctionne.

Maintenant que le modèle existe, on peut le remplir en ajoutant la couche d'administration à notre appli Flask. Je disais plus haut avoir triché: la couche d'administration s'obtient en créant d'abord la couche d'authentification (les utilisateurs), puis un instanciant un objet Admin sur l'application, puis en enregistrant les différentes classes qui seront gérées par l'administration.

.. code-block:: python

User = auth.User
admin = User(username='admin', admin=True, active=True)
admin.set_password('root')

Pour aller plus loin, il existe un exemple plus complet d'application avec Peewee dans la document du projet <http://peewee.readthedocs.org/en/latest/peewee/example.html>_.