10.5. Eine Steuerungs-API für die Kamera

Der Besitzer muss die Empfindlichkeit des Bewegungsmelders von überall aus einstellen können – an einem windigen Tag bewegt der Wind die Bäume stärker. Das bedeutet Routen, aus denen das Dashboard die aktuellen Einstellungen lesen und an die es Änderungen senden kann.

Ein kleines gemeinsames Zustands-Dict im Modul reicht aus, um die Stellgrößen zu halten. Spätere Seiten fügen ihm weitere Schlüssel hinzu; vorerst gibt es nur einen:

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

10.5.1. GET zum Lesen, POST zum Schreiben

Ein Paar von Routen – eine get, eine post – gibt dem Dashboard Lese-/Schreibzugriff auf 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 gibt den als JSON geparsten Body zurück oder None, wenn Content-Type nicht application/json war. Der post-Handler durchläuft jeden Fehlerfall – fehlender Schlüssel, falscher Typ, außerhalb des Bereichs – und bricht mit microdot.abort() ab, was microdot.HTTPException auslöst, um den Handler mit dem angegebenen Status und der angegebenen Nachricht kurzzuschließen.

10.5.2. GET, POST, PUT, DELETE

get() und post() sind die beiden, die wir am häufigsten verwenden werden. put() und delete() existieren für Fälle, die REST-Konventionen folgen – ein PUT /events/42, um Ereignis 42 zu ersetzen, ein DELETE /events/42, um es zu löschen. Der Handler ist ansonsten identisch.

10.5.3. Query-Strings und Formulare lesen

Das Dashboard sendet JSON, daher ist request.json das, was wir wollen. Zwei weitere Möglichkeiten, wie die Kamera Daten empfangen kann:

  • args – der Query-String. ?foo=1&bar=2 wird zu einem microdot.MultiDict, das Sie mit request.args.get('foo') lesen können.

  • form – ein HTML-Formular, das als application/x-www-form-urlencoded gesendet wird. Derselbe MultiDict-Typ.

Das MultiDict ist dict-ähnlich, lässt aber zu, dass ein Schlüssel mehrere Werte trägt (?tag=cat&tag=dog sind zwei tag-Werte); die vollständige Schnittstelle finden Sie unter microdot.MultiDict.

10.5.4. Dynamische URL-Segmente

Ein Routenpfad kann typisierte Platzhalter deklarieren, die microdot als zusätzliche Argumente an den Handler übergibt:

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

Die unterstützten Konverter sind <int:>, <re:> für einen benutzerdefinierten regulären Ausdruck, <path:> für ein Segment, das Schrägstriche enthalten kann, und der Standard (kein Präfix) für „alles bis zum nächsten Schrägstrich abgleichen“. <int:event_id> akzeptiert /events/42 und lehnt /events/abc ab – die Ablehnung wird zu einem 404, ohne dass der Handler ausgeführt wird.

10.5.5. Benutzerdefinierte Fehlerantworten

Der standardmäßige 404, den microdot sendet, ist ein schlichtes Not found. Das Dashboard erwartet JSON für jede Antwort; überschreiben Sie den 404-Handler, sodass auch er JSON zurückgibt:

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

errorhandler() nimmt entweder einen Statuscode entgegen (fängt jeden Fehler dieses Status ab) oder eine Ausnahmeklasse (fängt jeden Handler ab, der diese Ausnahme ausgelöst hat). Das (body, status)-Tupel schließt die Antwort kurz, ohne ein Response zu konstruieren.

Die Kamera stellt nun ihren Zustand bereit und akzeptiert Änderungen.