10.5. Une API de contrôle pour la caméra

Le propriétaire doit pouvoir régler la sensibilité du détecteur de mouvement depuis n’importe où – le vent agite davantage les arbres les jours de bourrasque. Cela implique des routes que le tableau de bord peut interroger pour lire les réglages actuels et auxquelles il peut envoyer des modifications.

Un petit dictionnaire d’état partagé sur le module suffit à conserver les réglages. Les pages suivantes y ajouteront d’autres clés ; pour l’instant, il n’y en a qu’une :

state = {
    'threshold': 12,
    'frame_count': 0,
    'trigger_count': 0,
}

10.5.1. GET pour lire, POST pour écrire

Une paire de routes – une get, une post – donne au tableau de bord un accès en lecture/écriture à state :

from microdot import abort

@app.get('/config')
async def get_config(request):
    return state

@app.post('/config')
async def set_config(request):
    body = request.json
    if not body or 'threshold' not in body:
        abort(400, 'missing threshold')
    try:
        threshold = int(body['threshold'])
    except (TypeError, ValueError):
        abort(400, 'threshold must be an integer')
    if not 0 <= threshold <= 100:
        abort(400, 'threshold out of range')
    state['threshold'] = threshold
    return {'ok': True, 'threshold': threshold}

microdot.Request.json renvoie le corps analysé en JSON, ou None si Content-Type n’était pas application/json. Le gestionnaire post parcourt chaque mode de défaillance – clé manquante, type incorrect, valeur hors plage – et abandonne avec microdot.abort(), qui lève microdot.HTTPException pour court-circuiter le gestionnaire avec le statut et le message donnés.

10.5.2. GET, POST, PUT, DELETE

get() et post() sont les deux que nous utiliserons le plus. put() et delete() existent pour les cas suivant les conventions REST – un PUT /events/42 pour remplacer l’événement 42, un DELETE /events/42 pour le supprimer. Le gestionnaire est par ailleurs identique.

10.5.3. Lire les chaînes de requête et les formulaires

Le tableau de bord envoie du JSON, donc request.json est ce qu’il nous faut. Deux autres façons dont la caméra peut recevoir des données :

  • args – la chaîne de requête. ?foo=1&bar=2 devient un microdot.MultiDict que vous pouvez lire avec request.args.get('foo').

  • form – un formulaire HTML envoyé en application/x-www-form-urlencoded. Même type MultiDict.

Le MultiDict se comporte comme un dictionnaire mais permet à une même clé de porter plusieurs valeurs (?tag=cat&tag=dog correspond à deux valeurs tag) ; consultez microdot.MultiDict pour l’interface complète.

10.5.4. Segments d’URL dynamiques

Un chemin de route peut déclarer des espaces réservés typés que microdot transmet au gestionnaire comme arguments supplémentaires :

@app.get('/events/<int:event_id>')
async def get_event(request, event_id):
    return {'id': event_id, 'msg': 'placeholder'}

Les convertisseurs pris en charge sont <int:>, <re:> pour une expression régulière personnalisée, <path:> pour un segment pouvant contenir des barres obliques, et celui par défaut (sans préfixe) pour « correspondre à tout jusqu’à la prochaine barre oblique ». <int:event_id> accepte /events/42 et rejette /events/abc – le rejet devient un 404 sans que le gestionnaire ne s’exécute.

10.5.5. Réponses d’erreur personnalisées

Le 404 par défaut envoyé par microdot est un simple Not found. Le tableau de bord attend du JSON pour chaque réponse ; remplacez le gestionnaire de 404 pour qu’il renvoie également du JSON :

@app.errorhandler(404)
async def not_found(request):
    return {'error': 'not found', 'path': request.path}, 404

errorhandler() prend soit un code de statut (intercepte toutes les erreurs de ce statut), soit une classe d’exception (intercepte tout gestionnaire ayant levé cette exception). Le tuple (body, status) court-circuite la réponse sans construire de Response.

La caméra expose désormais son état et accepte les modifications.