Асинхронный режим¶
AsyncAvitoClient повторяет доменную поверхность AvitoClient, но все сетевые
методы вызываются через await. Клиент обязательно открывается через async with:
в этот момент создаются httpx.AsyncClient, async locks и transport.
from avito import AsyncAvitoClient
async def load_profile() -> str | None:
async with AsyncAvitoClient.from_env() as avito:
profile = await avito.account().get_self()
return profile.name
Переписать sync-вызов на async¶
Sync:
from avito import AvitoClient
with AvitoClient.from_env() as avito:
orders = avito.order().list()
label = avito.order_label(task_id=42).download()
Async:
from avito import AsyncAvitoClient
async with AsyncAvitoClient.from_env() as avito:
orders = await avito.order().list()
label = await avito.order_label(task_id=42).download()
Для пагинации sync PaginatedList и async AsyncPaginatedList отличаются:
async-контейнер не является list, поэтому используйте async for или
await materialize().
async with AsyncAvitoClient.from_env() as avito:
page = await avito.ad(user_id=123).list(limit=100)
items = await page.materialize()
Тестирование без HTTP¶
from avito.testing import AsyncFakeTransport
async def test_orders_summary() -> None:
fake = (
AsyncFakeTransport()
.add_json("GET", "/order-management/1/orders", {"orders": []})
)
client = fake.as_client(user_id=123)
summary = await client.order_summary()
assert summary.total_orders == 0
await client.aclose()
Ограничения¶
AsyncPaginatedListне поддерживает list API и конкурентную итерацию одного экземпляра.- Бинарные ответы, включая PDF-этикетки заказов, загружаются целиком в память. Streaming API в версии 2.1.0 нет.
- Один
AsyncAvitoClientнельзя переносить между event loop. Создавайте клиент в том loop, где он будет использоваться.
Использование под ASGI (FastAPI / aiohttp / Starlette)¶
FastAPI lifespan¶
Создавайте клиент в lifespan, храните его в app.state и закрывайте на shutdown.
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from fastapi import Depends, FastAPI, Request
from avito import AsyncAvitoClient
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
async with AsyncAvitoClient.from_env() as avito:
app.state.avito = avito
yield
app = FastAPI(lifespan=lifespan)
def get_avito(request: Request) -> AsyncAvitoClient:
return request.app.state.avito
@app.get("/orders/summary")
async def orders_summary(avito: AsyncAvitoClient = Depends(get_avito)) -> dict[str, object]:
summary = await avito.order_summary()
return summary.to_dict()
aiohttp cleanup_ctx¶
from collections.abc import AsyncIterator
from aiohttp import web
from avito import AsyncAvitoClient
avito_key = web.AppKey("avito", AsyncAvitoClient)
async def avito_client_ctx(app: web.Application) -> AsyncIterator[None]:
async with AsyncAvitoClient.from_env() as avito:
app[avito_key] = avito
yield
async def orders_summary(request: web.Request) -> web.Response:
summary = await request.app[avito_key].order_summary()
return web.json_response(summary.to_dict())
app = web.Application()
app.cleanup_ctx.append(avito_client_ctx)
app.router.add_get("/orders/summary", orders_summary)
Per-worker isolation¶
Под Gunicorn/Uvicorn создавайте один AsyncAvitoClient на worker process. Не
создавайте клиент в master process до fork и не передавайте его между процессами:
у каждого worker свой event loop, connection pool и набор async locks.
Запрещённый паттерн¶
from avito import AsyncAvitoClient
avito = AsyncAvitoClient.from_env()
async def handler() -> dict[str, object]:
await avito.__aenter__() # ❌ loop-bound ресурсы создаются в request handler
return (await avito.order_summary()).to_dict()
Такой код привязывает внутренний httpx.AsyncClient к первому loop, который
коснулся handler. В тестах, background scheduler или другом worker loop это
приведёт к cross-loop ошибкам и утечкам соединений.
Background tasks¶
asyncio.create_task() и FastAPI BackgroundTasks, которые исполняются в том же
event loop, могут использовать app-level клиент из lifespan. Для process pool,
отдельного worker или внешнего scheduler создавайте отдельный AsyncAvitoClient
внутри этого процесса и его loop.