la-sérialisation.md 4.7 KB

Title: La sérialisation Category: Le développement web pour les nuls Tags: développement, Falcon, Python, web Summary: Lang: fr Status: draft


Maintenant que j'ai réalisé que les premiers pas avec SQLAlchemy ne se passaient pas aussi mal que je l'aurais pensé, vient le moment de faire interagir la base de données avec Falcon. Par exemple, renvoyer la liste des utilisateurs quand on fait un GET sur l'adresse /api/users/.

Aussi croyais-je bêtement pouvoir écrire un truc du genre :

:::python
from .models import User

class Resource(object):
    def on_get(self, req, resp):
        users = req.db.query(User).all()
        resp.body = json.dumps(users, ensure_ascii=False)

Mais bizarrement ce n'est pas aussi simple :

:::python
Traceback (most recent call last):
  File "/home/theenglishway/Documents/falcon/.venv/lib/python3.5/site-packages/gunicorn/workers/sync.py", line 135, in handle
    self.handle_request(listener, req, client, addr)
  File "/home/theenglishway/Documents/falcon/.venv/lib/python3.5/site-packages/gunicorn/workers/sync.py", line 176, in handle_request
    respiter = self.wsgi(environ, resp.start_response)
  File "/home/theenglishway/Documents/falcon/.venv/lib/python3.5/site-packages/falcon/api.py", line 244, in __call__
    responder(req, resp, **params)
  File "/home/theenglishway/Documents/falcon/myapp/users.py", line 10, in on_get
    resp.body = json.dumps(req.session.query(User).all(), ensure_ascii=False)
  File "/usr/lib/python3.5/json/__init__.py", line 237, in dumps
    **kw).encode(obj)
  File "/usr/lib/python3.5/json/encoder.py", line 198, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib/python3.5/json/encoder.py", line 256, in iterencode
    return _iterencode(o, 0)
  File "/usr/lib/python3.5/json/encoder.py", line 179, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: <User(name='way', fullname='theenglishway')> is not JSON serializable

Quelques problèmes se conjuguent dans ma tentative naïve qui était vouée à l'échec : une petite incompréhension de la réponse attendue par Falcon, et la sérialisation des objets renvoyées par la base de données.

L'objet Response de Falcon

Comme déjà dit dans un précédent article, Falcon n'est pas là pour nous mâcher le travail ... pour autant il peut quand même faire quelques petites choses pour nous si l'on utilise les bons champs de l'objet Response (l'objet Request dispose des mêmes champs).

Pour le corps de la réponse, on doit faire le choix d'utiliser un des champs suivants : media, body, data, ou stream.

stream

Comme son nom l'indique, un stream binaire avec une méthode read() permettant de le décoder. Utile pour les fichiers binaires de taille importante, j'imagine.

data

Une représentation binaire de la réponse. Pour les fichiers binaires de taille moindre, style image ?

body

Une représentation de la réponse sous forme de string ; Falcon nous fait alors la bonté de l'encoder comme il faut derrière nous.

media

Un objet de n'importe quel type, du moment qu'il est sérialisable, et qu'on a enregistré sa méthode de sérialisation auprès de Falcon, qui de base ne propose que la sérialisation JSON.

C'est donc très configurable mais de base un poil limité, et là aussi il y a du boulot à faire à la main. Mais la documentation est très bien fichue, et il suffit d'ajouter une classe avec des méthodes serialize / deserialize pour gérer n'importe quel type de contenu. C'est aussi là que l'on pourra le valider.

L'objet Request de Falcon

Ici aussi on dispose de quelques champs dans lesquels aller piocher le format qui nous intéresse : media, stream, ou bounded_stream.

stream

Ce champ est le miroir du champ du même nom dans l'objet Response. Attention toutefois, dans les cas limites le comportement de ce stream dépend du serveur WSGI employé, et il est possible de rester bloqué en lecture dans certains cas (si la requête est mal formée, par exemple).

bounded_stream

Une version "safe" et normalisée du stream brut, qui présente le même contenu et la même interface, mais sans les risques de blocage.

media

Là aussi c'est le miroir du champ du même nom de l'objet Response.

  1. Dans tous les cas, les objets renvoyés par SQLAlchemy ne sont pas sérialisables en JSON

Le premier problème n'est en plus un si l'on utilise l'attribut media plutôt que body.