Repository navigation
v0.6.2
Главное в этой версии — 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.