Перейти к содержанию

Практикум: описание API для расчёта доставки

Это краткое описание задачи и нужного ей API к статье о документации. Здесь собраны зависимости примера, правила работы и команда проверки. Полный справочник библиотеки находится отдельно.

Окружение и задача

Что нужно знать Правило
Пакет deadline-budget==0.1.3
Импорты from deadline_budget import BudgetContext, DeadlineExceededError
Окружение примера Python 3.13; asyncio.timeout() требует Python 3.11+
Методы библиотеки Синхронные: возвращают значения или выбрасывают исключения без await
Задача библиотеки Считать остаток времени и таймаут следующего вызова
Отмена Приложение использует asyncio.timeout(); сам бюджет ничего не отменяет

Приложение проверяет остаток товара и затем рассчитывает доставку с общим бюджетом 600 мс. Для каждого шага задан предел 400 мс. Если товара нет, результат равен None; иначе возвращается ответ адаптера доставки.

stock и shipping являются асинхронными функциями приложения, а не клиентами из deadline-budget. Обе принимают (sku, *, timeout), где таймаут задан в секундах. stock возвращает целое количество товара, а shipping возвращает расчёт доставки. Настоящие адаптеры должны применять переданный таймаут и не подавлять отмену. Практикум подставляет управляемые функции в том же процессе; HTTP-библиотека и развёрнутый склад здесь не проверяются.

Используемый API

  • BudgetContext.create(total_seconds, call_caps=None, *, min_timeout=0.1, safety_margin=0.0) синхронно возвращает BudgetContext.
  • ctx.remaining() возвращает остаток секунд как float; после истечения времени он может быть отрицательным.
  • ctx.timeout_for_call(call_name, reserve_for_next=0.0) возвращает секунды или выбрасывает DeadlineExceededError, если бюджет уже исчерпан.
  • Для неизвестного call_name отдельного ограничения нет. Опечатка в имени не вызывает ошибку конфигурации.
  • Минимум по умолчанию равен 100 мс и может превышать остаток времени. В примере явно заданы min_timeout=0 и отдельное ограничение всей операции.

Рабочий пример

import asyncio

from deadline_budget import BudgetContext


async def shipping_quote(sku, *, stock, shipping):
    ctx = BudgetContext.create(
        total_seconds=0.6,
        min_timeout=0,
        call_caps={"stock": 0.4, "shipping": 0.4},
    )
    async with asyncio.timeout(ctx.remaining()):
        available = await stock(sku, timeout=ctx.timeout_for_call("stock"))
        if available <= 0:
            return None
        return await shipping(sku, timeout=ctx.timeout_for_call("shipping"))

Один контекст создаётся на вызов функции и используется в обоих шагах. DeadlineExceededError означает, что расчёт бюджета не позволил начать следующую работу. TimeoutError может прийти от ограничения всей операции или от адаптера; пример передаёт оба типа ошибок вызывающему коду. Блокирующий код и код, подавляющий отмену, могут выйти за пределы таймаута asyncio.

Запуск проверок

С установленным uv выполните из корня репозитория сайта:

cd docs/blog/lab/2026-09-07-documentation-for-ai-coding-agents
uv run --no-project --python 3.13 --with-requirements requirements.txt python test_contract.py

Тесты на стандартной библиотеке проверяют версию и значения аргументов по умолчанию, ошибочный await перед синхронным методом, общий бюджет, ошибку с новым контекстом на каждом шаге из статьи, маленький остаток, который доходит до адаптера без изменений, отсутствие лимита для неизвестного имени, нулевой остаток товара, исчерпание времени перед следующим шагом и отличие расчёта от отмены. Код выше и его копии в английской и русской статьях сравниваются с shipping.py по синтаксическому дереву: если поменять только одну копию, проверка упадёт. Эти проверки синхронизации нужны тем, кто поддерживает страницу; самостоятельную реализацию оценивают поведенческие тесты.

Во всех тестах подменены часы внутри модуля библиотеки; тест сдвигает их вручную. Часы asyncio остаются настоящими. Отдельная проверка зависшего вызова дожидается реальной отмены по общему лимиту 600 мс; дополнительный таймаут в пять секунд защищает сам тест. Точные миллисекунды выполнения не проверяются.

Docker, внешний сервис и ключ API не нужны. Сеть нужна только для загрузки Python или зафиксированной версии пакета, если их нет в кеше. Несовпадение утверждения завершает запуск с ошибкой. Эти тесты проверяют документацию и код, а не качество работы ИИ-модели.

Файлы и дальнейшее чтение