Skip to content

v0.6.2

Choose a tag to compare

@borzov borzov released this 24 Aug 19:25
· 14 commits to main since this release

Главное в этой версии — SDK больше не заставляет переавторизовываться каждый раз, когда истёк токен. Он обновляет его сам.

Автообновление токена

Транспорт проверяет срок жизни токена перед запросом и обновляет его по refresh-токену, а если сервер всё же ответил 401 — обновляется и повторяет запрос ровно один раз. Отдельно вызывать ничего не нужно.

Сервер ротирует refresh_token при каждом обновлении, поэтому у клиента появились два новых параметра:

async with MegaplanClient(
    "https://example.megaplan.ru",
    access_token=saved.get("access_token"),
    refresh_token=saved.get("refresh_token"),
    on_token_refresh=save_tokens,   # sync или async
) as client:
    tasks = await client.tasks.list(limit=10)

on_token_refresh получает каждую свежую пару — это единственный способ пережить перезапуск процесса, потому что предыдущий refresh-токен после обновления мёртв. Исключение внутри колбэка логируется и не рушит запрос, но при автоматическом обновлении ротированную пару тогда уже никто не вернёт, так что сохранение стоит делать надёжным.

Попутно вскрылись три дефекта, жившие не один релиз:

  • ensure_authenticated() никогда не доходил до ветки обновления по refresh-токену: вход в метод требовал, чтобы токен истёк с буфером 60 секунд, а ветка внутри — чтобы он не истёк с буфером 3600. Всегда исполнялась авторизация по паролю.
  • Заголовки собирались один раз до цикла попыток, поэтому повтор после обновления ушёл бы со старым Authorization.
  • AuthResource держал собственный AuthManager, отдельный от клиентского. client.auth.get_access_token() из-за этого всегда возвращал None, а client.auth.clear_tokens() чистил не тот менеджер — «очищенный» клиент мог на следующем 401 незаметно воскресить всё ещё живой refresh-токен. Теперь менеджер один на клиент.

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

Ограничение: скачивание вложений (stream_binary()) обновляет токен только проактивно. Повтор по 401 на уже открытом стриме означал бы переоткрытие соединения — это вынесено за рамки версии.

Дела и контрагенты

  • todos.search_history() и contractors.search_history() — поиск по журналу, который у сделок, задач и проектов уже был. У контрагентов он идёт через конкретный подтип: абстрактный /contractor/{id} на этом маршруте отдаёт 500.
  • todos.get(fields=...) — дополнительные поля карточки, как у tasks.get().
  • todos.list() получил page_before, page_with, only_requested_fields и expand (responsible, user_created) — раньше был только page_after.
  • page_after у get_todos() пяти ресурсов: сделок, задач, проектов, сотрудников, контрагентов.
  • todos.list(q_in=["statement"]) больше не проходит молча. Такого поля у Todo нет, и сервер на него отвечал 200 с полной невыфильтрованной выборкой вместо ошибки — ровно та ловушка, которую уже чинили для задач и сделок.

Прочее

attachments.upload() читает файл в отдельном потоке вместо блокирующего чтения прямо на событийном цикле. Заодно это делает безопасным автоповтор запроса: в транспорт уходят прочитанные байты, а не дескриптор, который при повторе окажется уже вычитанным.

Полный список изменений — в CHANGELOG.