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é][sqlalchemy-intro],
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][falcon-media] 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`.


2. 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`.

[sqlalchemy-intro]: {filename}sqlalchemy-intro.md
[falcon-media]: https://falcon.readthedocs.io/en/stable/api/media.html#media
