10.5. Una API de control para la cámara#

El propietario necesita ajustar la sensibilidad del detector de movimiento desde cualquier lugar: el viento mueve más los árboles en un día ventoso. Eso significa rutas desde las que el panel de control pueda leer la configuración actual y a las que pueda enviar cambios.

Un pequeño diccionario de estado compartido en el módulo basta para guardar los controles. Las páginas posteriores le añaden más claves; por ahora solo hay una:

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

10.5.1. GET para leer, POST para escribir#

Un par de rutas – una get y una post – da al panel de control acceso de lectura/escritura a 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 devuelve el cuerpo analizado como JSON, o None si Content-Type no era application/json. El manejador post recorre cada modo de fallo – clave faltante, tipo incorrecto, fuera de rango – y aborta con microdot.abort(), que lanza microdot.HTTPException para cortocircuitar el manejador con el estado y el mensaje indicados.

10.5.2. GET, POST, PUT, DELETE#

get() y post() son los dos que usaremos más. put() y delete() existen para casos que siguen las convenciones REST: un PUT /events/42 para reemplazar el evento 42, un DELETE /events/42 para eliminarlo. El manejador es por lo demás idéntico.

10.5.3. Leer cadenas de consulta y formularios#

El panel de control envía JSON, así que request.json es lo que queremos. Otras dos maneras en que la cámara podría recibir datos:

  • args – la cadena de consulta. ?foo=1&bar=2 se convierte en un microdot.MultiDict que puedes leer con request.args.get('foo').

  • form – un formulario HTML enviado como application/x-www-form-urlencoded. El mismo tipo MultiDict.

El MultiDict es similar a un diccionario, pero permite que una clave lleve varios valores (?tag=cat&tag=dog son dos valores de tag); consulta microdot.MultiDict para ver la superficie completa.

10.5.4. Segmentos de URL dinámicos#

La ruta de un endpoint puede declarar marcadores de posición con tipo que microdot pasa al manejador como argumentos adicionales:

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

Los convertidores admitidos son <int:>, <re:> para una expresión regular personalizada, <path:> para un segmento que puede contener barras, y el predeterminado (sin prefijo) para «coincidir con cualquier cosa hasta la siguiente barra». <int:event_id> acepta /events/42 y rechaza /events/abc; el rechazo se convierte en un 404 sin que el manejador llegue a ejecutarse.

10.5.5. Respuestas de error personalizadas#

El 404 predeterminado que envía microdot es un simple Not found. El panel de control espera JSON en cada respuesta; sobrescribe el manejador del 404 para que también devuelva JSON:

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

errorhandler() toma o bien un código de estado (captura todos los errores de ese estado) o bien una clase de excepción (captura todo manejador que lanzó esa excepción). La tupla (body, status) cortocircuita la respuesta sin construir un Response.

La cámara ahora expone su estado y acepta modificaciones.