10.5. Un’API di controllo per la camera

Il proprietario ha bisogno di impostare la sensibilità del rilevatore di movimento da qualunque posizione: nelle giornate ventose il vento muove di più gli alberi. Questo richiede delle route da cui la dashboard possa leggere le impostazioni correnti e a cui inviare modifiche.

Un piccolo dict di stato condiviso a livello di modulo è sufficiente a contenere i parametri. Le pagine successive vi aggiungono altre chiavi; per ora ce n’è una:

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

10.5.1. GET per leggere, POST per scrivere

Una coppia di route – una get, una post – offre alla dashboard accesso in lettura/scrittura a 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 restituisce il corpo interpretato come JSON, oppure None se il Content-Type non era application/json. L’handler post percorre ogni modalità di errore – chiave mancante, tipo errato, valore fuori intervallo – e si interrompe con microdot.abort(), che solleva microdot.HTTPException per interrompere immediatamente l’handler con lo stato e il messaggio indicati.

10.5.2. GET, POST, PUT, DELETE

get() e post() sono i due che useremo più spesso. put() e delete() esistono per i casi che seguono le convenzioni REST: un PUT /events/42 per sostituire l’evento 42, un DELETE /events/42 per eliminarlo. Per il resto l’handler è identico.

10.5.3. Lettura di query string e form

La dashboard invia JSON, quindi request.json è ciò che ci serve. Altri due modi in cui la camera potrebbe ricevere dati:

  • args – la query string. ?foo=1&bar=2 diventa un microdot.MultiDict che puoi leggere con request.args.get('foo').

  • form – un form HTML inviato come application/x-www-form-urlencoded. Lo stesso tipo MultiDict.

Il MultiDict è simile a un dict, ma consente a una stessa chiave di contenere più valori (?tag=cat&tag=dog sono due valori tag); consulta microdot.MultiDict per l’interfaccia completa.

10.5.4. Segmenti di URL dinamici

Il percorso di una route può dichiarare segnaposto tipizzati che microdot passa all’handler come argomenti aggiuntivi:

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

I convertitori supportati sono <int:>, <re:> per una regex personalizzata, <path:> per un segmento che può contenere slash, e quello predefinito (nessun prefisso) per «corrisponde a qualsiasi cosa fino allo slash successivo». <int:event_id> accetta /events/42 e rifiuta /events/abc: il rifiuto si traduce in un 404 senza che l’handler venga eseguito.

10.5.5. Risposte di errore personalizzate

Il 404 predefinito che microdot invia è un semplice Not found. La dashboard si aspetta JSON per ogni risposta; sovrascrivi l’handler del 404 in modo che restituisca anch’esso JSON:

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

errorhandler() accetta o un codice di stato (intercetta ogni errore con quello stato) o una classe di eccezione (intercetta ogni handler che ha sollevato quell’eccezione). La tupla (body, status) produce la risposta immediatamente senza costruire un oggetto Response.

La camera ora espone il proprio stato e accetta modifiche.