10.5. Een besturings-API voor de cam

De eigenaar moet de gevoeligheid van de bewegingsdetector overal vandaan kunnen instellen – de wind beweegt bomen meer op een winderige dag. Dat betekent routes waaruit het dashboard de huidige instellingen kan lezen en waaraan het wijzigingen kan posten.

Een kleine gedeelde state-dict op de module is genoeg om de knoppen te bevatten. Latere pagina’s voegen er meer sleutels aan toe; voorlopig is er één:

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

10.5.1. GET om te lezen, POST om te schrijven

Een paar routes – één get en één post – geeft het dashboard lees- en schrijftoegang tot 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 geeft de body terug, geparseerd als JSON, of None als Content-Type niet application/json was. De post-handler loopt elke faalmodus af – ontbrekende sleutel, verkeerd type, buiten bereik – en breekt af met microdot.abort(), die een microdot.HTTPException opwerpt om de handler kort te sluiten met de opgegeven status en melding.

10.5.2. GET, POST, PUT, DELETE

get() en post() zijn de twee die we het meest zullen gebruiken. put() en delete() bestaan voor gevallen die de REST-conventies volgen – een PUT /events/42 om event 42 te vervangen, een DELETE /events/42 om het te verwijderen. De handler is verder identiek.

10.5.3. Query strings en formulieren lezen

Het dashboard post JSON, dus request.json is wat we willen. Twee andere manieren waarop de cam gegevens kan ontvangen:

  • args – de query string. ?foo=1&bar=2 wordt een microdot.MultiDict die je kunt lezen met request.args.get('foo').

  • form – een HTML-formulier dat als application/x-www-form-urlencoded is gepost. Hetzelfde MultiDict-type.

De MultiDict is dict-achtig, maar laat één sleutel meerdere waarden dragen (?tag=cat&tag=dog zijn twee tag-waarden); zie microdot.MultiDict voor het volledige oppervlak.

10.5.4. Dynamische URL-segmenten

Een routepad kan getypeerde plaatshouders declareren die microdot als extra argumenten aan de handler doorgeeft:

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

De ondersteunde converters zijn <int:>, <re:> voor een aangepaste regex, <path:> voor een segment dat schuine strepen kan bevatten, en de standaard (geen prefix) voor “match alles tot de volgende schuine streep.” <int:event_id> accepteert /events/42 en weigert /events/abc – de weigering wordt een 404 zonder dat de handler draait.

10.5.5. Aangepaste foutresponses

De standaard 404 die microdot stuurt is gewoon Not found. Het dashboard verwacht JSON voor elke response; overschrijf de 404-handler zodat hij ook JSON teruggeeft:

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

errorhandler() neemt ofwel een statuscode (vangt elke fout van die status) ofwel een uitzonderingsklasse (vangt elke handler die die uitzondering opwierp). De (body, status)-tuple sluit de response kort zonder een Response te construeren.

De cam stelt nu zijn state beschikbaar en accepteert wijzigingen.