10.5. Un API de control pentru cameră

Proprietarul are nevoie să seteze sensibilitatea detectorului de mișcare de oriunde – vântul mișcă mai mult copacii într-o zi cu vânt. Asta înseamnă rute din care tabloul de bord poate citi setările curente și către care poate trimite modificări.

Un mic dicționar de stare partajat pe modul este suficient pentru a păstra reglajele. Paginile ulterioare adaugă mai multe chei la el; deocamdată există una singură:

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

10.5.1. GET pentru citire, POST pentru scriere

O pereche de rute – una get, una post – oferă tabloului de bord acces de citire/scriere la 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 returnează corpul analizat ca JSON, sau None dacă Content-Type nu a fost application/json. Handlerul post parcurge fiecare mod de eșec – cheie lipsă, tip greșit, valoare în afara intervalului – și abandonează cu microdot.abort(), care ridică microdot.HTTPException pentru a scurtcircuita handlerul cu statusul și mesajul date.

10.5.2. GET, POST, PUT, DELETE

get() și post() sunt cele două pe care le vom folosi cel mai mult. put() și delete() există pentru cazurile care urmează convențiile REST – un PUT /events/42 pentru a înlocui evenimentul 42, un DELETE /events/42 pentru a-l elimina. Handlerul este în rest identic.

10.5.3. Citirea șirurilor de interogare și a formularelor

Tabloul de bord trimite JSON, așa că request.json este ceea ce dorim. Alte două moduri în care camera ar putea primi date:

  • args – șirul de interogare. ?foo=1&bar=2 devine un microdot.MultiDict pe care îl poți citi cu request.args.get('foo').

  • form – un formular HTML trimis ca application/x-www-form-urlencoded. Același tip MultiDict.

MultiDict este asemănător unui dicționar, dar permite unei chei să poarte mai multe valori (?tag=cat&tag=dog reprezintă două valori tag); vezi microdot.MultiDict pentru suprafața completă.

10.5.4. Segmente de URL dinamice

O cale de rută poate declara substituenți tipizați pe care microdot îi transmite handlerului ca argumente suplimentare:

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

Convertoarele acceptate sunt <int:>, <re:> pentru o expresie regulată personalizată, <path:> pentru un segment care poate conține bare oblice și cel implicit (fără prefix) pentru „potrivește orice până la următoarea bară oblică.” <int:event_id> acceptă /events/42 și respinge /events/abc – respingerea devine un 404 fără ca handlerul să ruleze.

10.5.5. Răspunsuri de eroare personalizate

Răspunsul 404 implicit pe care îl trimite microdot este pur și simplu Not found. Tabloul de bord așteaptă JSON pentru fiecare răspuns; suprascrie handlerul de 404 astfel încât să returneze și el JSON:

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

errorhandler() acceptă fie un cod de status (prinde fiecare eroare cu acel status), fie o clasă de excepție (prinde fiecare handler care a ridicat acea excepție). Tuplul (body, status) scurtcircuitează răspunsul fără a construi un Response.

Camera își expune acum starea și acceptă modificări.