10.5. API управления для камеры

Владельцу нужно настраивать чувствительность детектора движения откуда угодно – в ветреный день ветер сильнее раскачивает деревья. Это означает маршруты, из которых панель управления может читать текущие настройки и в которые может отправлять изменения.

Небольшого общего словаря состояния на уровне модуля достаточно, чтобы хранить настройки. На последующих страницах в него добавляются другие ключи; пока есть один:

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

10.5.1. GET для чтения, POST для записи

Пара маршрутов – один get, один post – даёт панели управления доступ к 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 возвращает тело, разобранное как JSON, или None, если Content-Type не был application/json. Обработчик post проходит по каждому режиму отказа – отсутствующий ключ, неверный тип, выход за пределы диапазона – и прерывается через microdot.abort(), который возбуждает microdot.HTTPException, чтобы досрочно завершить обработчик с заданным статусом и сообщением.

10.5.2. GET, POST, PUT, DELETE

get() и post() – те два, которые мы будем использовать чаще всего. put() и delete() существуют для случаев, следующих соглашениям REST – PUT /events/42 для замены события 42, DELETE /events/42 для его удаления. В остальном обработчик идентичен.

10.5.3. Чтение строк запроса и форм

Панель управления отправляет JSON, так что нам нужен request.json. Два других способа, которыми камера может получать данные:

  • args – строка запроса. ?foo=1&bar=2 становится объектом microdot.MultiDict, который можно читать через request.args.get('foo').

  • form – HTML-форма, отправленная как application/x-www-form-urlencoded. Тот же тип MultiDict.

MultiDict похож на словарь, но позволяет одному ключу нести несколько значений (?tag=cat&tag=dog – это два значения tag); полный интерфейс см. в microdot.MultiDict.

10.5.4. Динамические сегменты URL

Путь маршрута может объявлять типизированные плейсхолдеры, которые microdot передаёт обработчику как дополнительные аргументы:

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

Поддерживаемые преобразователи: <int:>, <re:> для произвольного регулярного выражения, <path:> для сегмента, который может содержать слеши, и значение по умолчанию (без префикса) для «совпадения с чем угодно вплоть до следующего слеша». <int:event_id> принимает /events/42 и отклоняет /events/abc – отклонение становится ответом 404 без запуска обработчика.

10.5.5. Пользовательские ответы об ошибках

По умолчанию microdot отправляет в ответе 404 обычный текст Not found. Панель управления ожидает JSON для каждого ответа; переопределите обработчик 404, чтобы он тоже возвращал JSON:

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

errorhandler() принимает либо код статуса (перехватывает каждую ошибку с этим статусом), либо класс исключения (перехватывает каждый обработчик, возбудивший это исключение). Кортеж (body, status) досрочно формирует ответ без создания Response.

Теперь камера предоставляет своё состояние и принимает правки.