10.5. Uma API de controle para a câmera

O proprietário precisa ajustar a sensibilidade do detector de movimento de qualquer lugar – o vento move as árvores mais em um dia ventoso. Isso significa rotas das quais o painel possa ler as configurações atuais e às quais possa enviar mudanças.

Um pequeno dict de estado compartilhado no módulo é suficiente para guardar os controles. Páginas posteriores adicionam mais chaves a ele; por enquanto há uma:

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

10.5.1. GET para ler, POST para escrever

Um par de rotas – uma get, uma post – dá ao painel 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 retorna o corpo analisado como JSON, ou None se o Content-Type não era application/json. O handler post percorre cada modo de falha – chave ausente, tipo errado, fora do intervalo – e desiste com microdot.abort(), que lança microdot.HTTPException para interromper o handler com o status e a mensagem fornecidos.

10.5.2. GET, POST, PUT, DELETE

get() e post() são os dois que mais usaremos. put() e delete() existem para casos que seguem as convenções REST – um PUT /events/42 para substituir o evento 42, um DELETE /events/42 para removê-lo. O handler é idêntico no restante.

10.5.3. Lendo query strings e formulários

O painel envia JSON, então request.json é o que queremos. Duas outras formas pelas quais a câmera pode receber dados:

  • args – a query string. ?foo=1&bar=2 torna-se um microdot.MultiDict que você pode ler 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 dict, mas permite que uma chave carregue múltiplos valores (?tag=cat&tag=dog são dois valores de tag); veja microdot.MultiDict para a interface completa.

10.5.4. Segmentos dinâmicos de URL

Um caminho de rota pode declarar marcadores de posição tipados que o microdot passa ao handler como argumentos extras:

@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 um regex personalizado, <path:> para um segmento que pode conter barras, e o padrão (sem prefixo) para “corresponder a qualquer coisa até a próxima barra”. <int:event_id> aceita /events/42 e rejeita /events/abc – a rejeição vira um 404 sem que o handler seja executado.

10.5.5. Respostas de erro personalizadas

O 404 padrão que o microdot envia é um simples Not found. O painel espera JSON em toda resposta; sobrescreva o handler de 404 para que ele também retorne JSON:

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

errorhandler() recebe ou um código de status (captura todo erro daquele status) ou uma classe de exceção (captura todo handler que lançou aquela exceção). A tupla (body, status) interrompe a resposta sem construir um Response.

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