10.5. Uma API de controlo para a câmara

O proprietário precisa de ajustar a sensibilidade do detetor de movimento a partir de qualquer lugar – o vento move as árvores mais num dia com vento. Isso implica rotas a partir das quais o painel de controlo pode ler as definições atuais e publicar alterações.

Um pequeno dicionário de estado partilhado no módulo é suficiente para guardar os parâmetros. As páginas seguintes adicionam mais chaves; por agora há apenas uma:

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

10.5.1. GET para ler, POST para escrever

Um par de rotas – um get, um post – dá ao painel de controlo acesso de leitura/escrita ao 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 devolve o corpo analisado como JSON, ou None se o Content-Type não for application/json. O handler post percorre cada modo de falha – chave em falta, tipo errado, fora do intervalo – e interrompe com microdot.abort(), que lança microdot.HTTPException para curto-circuitar o handler com o estado e a mensagem indicados.

10.5.2. GET, POST, PUT, DELETE

get() e post() são os dois que mais utilizaremos. put() e delete() existem para casos que seguem convenções REST – um PUT /events/42 para substituir o evento 42, um DELETE /events/42 para o remover. O handler é, de resto, idêntico.

10.5.3. Ler query strings e formulários

O painel de controlo envia JSON, pelo que request.json é o que queremos. Duas outras formas como a câmara pode receber dados:

  • args – a query string. ?foo=1&bar=2 torna-se um microdot.MultiDict que pode ser lido com request.args.get('foo').

  • form – um formulário HTML enviado como application/x-www-form-urlencoded. Mesmo tipo MultiDict.

O MultiDict é semelhante a um dicionário, mas permite que uma chave tenha múltiplos valores (?tag=cat&tag=dog são dois valores tag); consulte microdot.MultiDict para a superfície completa.

10.5.4. Segmentos de URL dinâmicos

Um caminho de rota pode declarar espaços reservados com tipo que o microdot passa ao handler como argumentos extra:

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

Os conversores suportados são <int:>, <re:> para uma regex personalizada, <path:> para um segmento que pode conter barras, e o predefinido (sem prefixo) para «correspondência com qualquer coisa até à próxima barra». <int:event_id> aceita /events/42 e rejeita /events/abc – a rejeição resulta num 404 sem o handler ser executado.

10.5.5. Respostas de erro personalizadas

O 404 predefinido que o microdot envia é apenas Not found. O painel de controlo espera JSON em todas as respostas; substitua o handler 404 para que também devolva JSON:

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

errorhandler() aceita um código de estado (interceta todos os erros com esse estado) ou uma classe de exceção (interceta todos os handlers que lançaram essa exceção). O tuplo (body, status) curto-circuita a resposta sem construir um Response.

A câmara expõe agora o seu estado e aceita edições.